From c273fd562dc29c2e6b1533110368a59586859e56 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 13:12:31 +0000 Subject: [PATCH 1/7] Initial plan From 59825780356a5e0b9caa60ebb1b5fbee821c057e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 13:19:43 +0000 Subject: [PATCH 2/7] Initial plan for GH_AW_WORKFLOW_ID fix Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/aw/schemas/agentic-workflow.json | 5311 +++++++++++++++++ .github/workflows/ai-moderator.lock.yml | 364 +- .github/workflows/archie.lock.yml | 597 +- .github/workflows/artifacts-summary.lock.yml | 364 +- .github/workflows/audit-workflows.lock.yml | 364 +- .github/workflows/blog-auditor.lock.yml | 364 +- .github/workflows/brave.lock.yml | 597 +- .../breaking-change-checker.lock.yml | 364 +- .github/workflows/campaign-generator.lock.yml | 364 +- .github/workflows/changeset.lock.yml | 597 +- .github/workflows/ci-coach.lock.yml | 364 +- .github/workflows/ci-doctor.lock.yml | 364 +- .../cli-consistency-checker.lock.yml | 364 +- .../workflows/cli-version-checker.lock.yml | 364 +- .github/workflows/cloclo.lock.yml | 597 +- .../workflows/close-old-discussions.lock.yml | 364 +- .../commit-changes-analyzer.lock.yml | 364 +- .../workflows/copilot-agent-analysis.lock.yml | 364 +- .../copilot-pr-merged-report.lock.yml | 364 +- .../copilot-pr-nlp-analysis.lock.yml | 364 +- .../copilot-pr-prompt-analysis.lock.yml | 364 +- .../copilot-session-insights.lock.yml | 364 +- .github/workflows/craft.lock.yml | 597 +- .../daily-assign-issue-to-user.lock.yml | 364 +- .github/workflows/daily-code-metrics.lock.yml | 364 +- .../daily-copilot-token-report.lock.yml | 364 +- .github/workflows/daily-doc-updater.lock.yml | 364 +- .github/workflows/daily-fact.lock.yml | 364 +- .github/workflows/daily-file-diet.lock.yml | 364 +- .../workflows/daily-firewall-report.lock.yml | 364 +- .../workflows/daily-issues-report.lock.yml | 364 +- .../daily-malicious-code-scan.lock.yml | 364 +- .../daily-multi-device-docs-tester.lock.yml | 364 +- .github/workflows/daily-news.lock.yml | 364 +- .../daily-performance-summary.lock.yml | 364 +- .../workflows/daily-repo-chronicle.lock.yml | 364 +- .github/workflows/daily-team-status.lock.yml | 364 +- .../workflows/daily-workflow-updater.lock.yml | 364 +- .github/workflows/deep-report.lock.yml | 364 +- .../workflows/dependabot-go-checker.lock.yml | 364 +- .github/workflows/dev-hawk.lock.yml | 364 +- .github/workflows/dev.lock.yml | 364 +- .../developer-docs-consolidator.lock.yml | 364 +- .github/workflows/dictation-prompt.lock.yml | 364 +- .github/workflows/docs-noob-tester.lock.yml | 364 +- .../duplicate-code-detector.lock.yml | 364 +- .../example-workflow-analyzer.lock.yml | 364 +- .../github-mcp-structural-analysis.lock.yml | 364 +- .../github-mcp-tools-report.lock.yml | 364 +- .../workflows/glossary-maintainer.lock.yml | 364 +- .github/workflows/go-fan.lock.yml | 364 +- ...ze-reduction-project64.campaign.g.lock.yml | 364 +- ...go-file-size-reduction.campaign.g.lock.yml | 364 +- .github/workflows/go-logger.lock.yml | 364 +- .../workflows/go-pattern-detector.lock.yml | 364 +- .github/workflows/grumpy-reviewer.lock.yml | 597 +- .github/workflows/hourly-ci-cleaner.lock.yml | 364 +- .../workflows/human-ai-collaboration.lock.yml | 364 +- .github/workflows/incident-response.lock.yml | 364 +- .../workflows/instructions-janitor.lock.yml | 364 +- .github/workflows/intelligence.lock.yml | 364 +- .github/workflows/issue-arborist.lock.yml | 364 +- .github/workflows/issue-classifier.lock.yml | 597 +- .github/workflows/issue-monster.lock.yml | 364 +- .github/workflows/issue-triage-agent.lock.yml | 364 +- .github/workflows/jsweep.lock.yml | 364 +- .../workflows/layout-spec-maintainer.lock.yml | 364 +- .github/workflows/lockfile-stats.lock.yml | 364 +- .github/workflows/mcp-inspector.lock.yml | 364 +- .github/workflows/mergefest.lock.yml | 364 +- .../workflows/notion-issue-summary.lock.yml | 364 +- .github/workflows/org-health-report.lock.yml | 364 +- .github/workflows/org-wide-rollout.lock.yml | 364 +- .github/workflows/pdf-summary.lock.yml | 597 +- .github/workflows/plan.lock.yml | 597 +- .github/workflows/poem-bot.lock.yml | 597 +- .github/workflows/portfolio-analyst.lock.yml | 364 +- .../workflows/pr-nitpick-reviewer.lock.yml | 364 +- .../prompt-clustering-analysis.lock.yml | 364 +- .github/workflows/python-data-charts.lock.yml | 364 +- .github/workflows/q.lock.yml | 597 +- .github/workflows/release.lock.yml | 364 +- .github/workflows/repo-tree-map.lock.yml | 364 +- .../repository-quality-improver.lock.yml | 364 +- .github/workflows/research.lock.yml | 364 +- .github/workflows/safe-output-health.lock.yml | 364 +- .../schema-consistency-checker.lock.yml | 364 +- .github/workflows/scout.lock.yml | 597 +- .../workflows/security-compliance.lock.yml | 364 +- .github/workflows/security-fix-pr.lock.yml | 364 +- .../semantic-function-refactor.lock.yml | 364 +- .../workflows/slide-deck-maintainer.lock.yml | 364 +- .github/workflows/smoke-claude.lock.yml | 364 +- .../workflows/smoke-codex-firewall.lock.yml | 364 +- .github/workflows/smoke-codex.lock.yml | 364 +- .../smoke-copilot-no-firewall.lock.yml | 364 +- .../smoke-copilot-playwright.lock.yml | 364 +- .../smoke-copilot-safe-inputs.lock.yml | 364 +- .github/workflows/smoke-copilot.lock.yml | 364 +- .github/workflows/smoke-detector.lock.yml | 364 +- .github/workflows/smoke-srt.lock.yml | 364 +- .github/workflows/spec-kit-execute.lock.yml | 364 +- .github/workflows/spec-kit-executor.lock.yml | 364 +- .github/workflows/speckit-dispatcher.lock.yml | 597 +- .../workflows/stale-repo-identifier.lock.yml | 364 +- .../workflows/static-analysis-report.lock.yml | 364 +- .github/workflows/sub-issue-closer.lock.yml | 364 +- .github/workflows/super-linter.lock.yml | 364 +- .../workflows/technical-doc-writer.lock.yml | 364 +- .github/workflows/tidy.lock.yml | 364 +- .github/workflows/typist.lock.yml | 364 +- .github/workflows/unbloat-docs.lock.yml | 364 +- .github/workflows/video-analyzer.lock.yml | 364 +- .../workflows/weekly-issue-summary.lock.yml | 364 +- .github/workflows/workflow-generator.lock.yml | 364 +- .vscode/settings.json | 1 + 116 files changed, 21008 insertions(+), 28829 deletions(-) create mode 100644 .github/aw/schemas/agentic-workflow.json diff --git a/.github/aw/schemas/agentic-workflow.json b/.github/aw/schemas/agentic-workflow.json new file mode 100644 index 00000000000..465d9fe2a64 --- /dev/null +++ b/.github/aw/schemas/agentic-workflow.json @@ -0,0 +1,5311 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["on"], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Workflow name that appears in the GitHub Actions interface. If not specified, defaults to the filename without extension.", + "examples": ["Copilot Agent PR Analysis", "Dev Hawk", "Smoke Claude"] + }, + "description": { + "type": "string", + "description": "Optional workflow description that is rendered as a comment in the generated GitHub Actions YAML file (.lock.yml)", + "examples": ["Quickstart for using the GitHub Actions library"] + }, + "source": { + "type": "string", + "description": "Optional source reference indicating where this workflow was added from. Format: owner/repo/path@ref (e.g., githubnext/agentics/workflows/ci-doctor.md@v1.0.0). Rendered as a comment in the generated lock file.", + "examples": ["githubnext/agentics/workflows/ci-doctor.md", "githubnext/agentics/workflows/daily-perf-improver.md@1f181b37d3fe5862ab590648f25a292e345b5de6"] + }, + "tracker-id": { + "type": "string", + "minLength": 8, + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "Optional tracker identifier to tag all created assets (issues, discussions, comments, pull requests). Must be at least 8 characters and contain only alphanumeric characters, hyphens, and underscores. This identifier will be inserted in the body/description of all created assets to enable searching and retrieving assets associated with this workflow.", + "examples": ["workflow-2024-q1", "team-alpha-bot", "security_audit_v2"] + }, + "labels": { + "type": "array", + "description": "Optional array of labels to categorize and organize workflows. Labels can be used to filter workflows in status/list commands.", + "items": { + "type": "string", + "minLength": 1 + }, + "examples": [ + ["automation", "security"], + ["docs", "maintenance"], + ["ci", "testing"] + ] + }, + "metadata": { + "type": "object", + "description": "Optional metadata field for storing custom key-value pairs compatible with the custom agent spec. Key names are limited to 64 characters, and values are limited to 1024 characters.", + "patternProperties": { + "^.{1,64}$": { + "type": "string", + "maxLength": 1024, + "description": "Metadata value (maximum 1024 characters)" + } + }, + "additionalProperties": false, + "examples": [ + { + "author": "John Doe", + "version": "1.0.0", + "category": "automation" + } + ] + }, + "imports": { + "type": "array", + "description": "Optional array of workflow specifications to import (similar to @include directives but defined in frontmatter). Format: owner/repo/path@ref (e.g., githubnext/agentics/workflows/shared/common.md@v1.0.0). Can be strings or objects with path and inputs. Any markdown files under .github/agents directory are treated as custom agent files and only one agent file is allowed per workflow.", + "items": { + "oneOf": [ + { + "type": "string", + "description": "Workflow specification in format owner/repo/path@ref. Markdown files under .github/agents/ are treated as agent configuration files." + }, + { + "type": "object", + "description": "Import specification with path and optional inputs", + "required": ["path"], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "description": "Workflow specification in format owner/repo/path@ref. Markdown files under .github/agents/ are treated as agent configuration files." + }, + "inputs": { + "type": "object", + "description": "Input values to pass to the imported workflow. Keys are input names declared in the imported workflow's inputs section, values can be strings or expressions.", + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + } + } + } + } + ] + }, + "examples": [ + ["shared/jqschema.md", "shared/reporting.md"], + ["shared/mcp/gh-aw.md", "shared/jqschema.md", "shared/reporting.md"], + ["../instructions/documentation.instructions.md"], + [".github/agents/my-agent.md"], + [ + { + "path": "shared/discussions-data-fetch.md", + "inputs": { + "count": 50 + } + } + ] + ] + }, + "on": { + "description": "Workflow triggers that define when the agentic workflow should run. Supports standard GitHub Actions trigger events plus special command triggers for /commands (required)", + "oneOf": [ + { + "type": "string", + "minLength": 1, + "description": "Simple trigger event name (e.g., 'push', 'issues', 'pull_request', 'discussion', 'schedule', 'fork', 'create', 'delete', 'public', 'watch', 'workflow_call')", + "examples": ["push", "issues", "workflow_dispatch"] + }, + { + "type": "object", + "description": "Complex trigger configuration with event-specific filters and options", + "properties": { + "slash_command": { + "description": "Special slash command trigger for /command workflows (e.g., '/my-bot' in issue comments). Creates conditions to match slash commands automatically.", + "oneOf": [ + { + "type": "null", + "description": "Null command configuration - defaults to using the workflow filename (without .md extension) as the command name" + }, + { + "type": "string", + "minLength": 1, + "pattern": "^[^/]", + "description": "Command name as a string (shorthand format, e.g., 'customname' for '/customname' triggers). Command names must not start with '/' as the slash is automatically added when matching commands." + }, + { + "type": "object", + "description": "Command configuration object with custom command name", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "pattern": "^[^/]", + "description": "Custom command name for slash commands (e.g., 'helper-bot' for '/helper-bot' triggers). Command names must not start with '/' as the slash is automatically added when matching commands. Defaults to workflow filename without .md extension if not specified." + }, + "events": { + "description": "Events where the command should be active. Default is all comment-related events ('*'). Use GitHub Actions event names.", + "oneOf": [ + { + "type": "string", + "description": "Single event name or '*' for all events. Use GitHub Actions event names: 'issues', 'issue_comment', 'pull_request_comment', 'pull_request', 'pull_request_review_comment', 'discussion', 'discussion_comment'.", + "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"] + }, + { + "type": "array", + "minItems": 1, + "description": "Array of event names where the command should be active (requires at least one). Use GitHub Actions event names.", + "items": { + "type": "string", + "description": "GitHub Actions event name.", + "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"] + } + } + ] + } + }, + "additionalProperties": false + } + ] + }, + "command": { + "description": "DEPRECATED: Use 'slash_command' instead. Special command trigger for /command workflows (e.g., '/my-bot' in issue comments). Creates conditions to match slash commands automatically.", + "oneOf": [ + { + "type": "null", + "description": "Null command configuration - defaults to using the workflow filename (without .md extension) as the command name" + }, + { + "type": "string", + "minLength": 1, + "pattern": "^[^/]", + "description": "Command name as a string (shorthand format, e.g., 'customname' for '/customname' triggers). Command names must not start with '/' as the slash is automatically added when matching commands." + }, + { + "type": "object", + "description": "Command configuration object with custom command name", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "pattern": "^[^/]", + "description": "Custom command name for slash commands (e.g., 'helper-bot' for '/helper-bot' triggers). Command names must not start with '/' as the slash is automatically added when matching commands. Defaults to workflow filename without .md extension if not specified." + }, + "events": { + "description": "Events where the command should be active. Default is all comment-related events ('*'). Use GitHub Actions event names.", + "oneOf": [ + { + "type": "string", + "description": "Single event name or '*' for all events. Use GitHub Actions event names: 'issues', 'issue_comment', 'pull_request_comment', 'pull_request', 'pull_request_review_comment', 'discussion', 'discussion_comment'.", + "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"] + }, + { + "type": "array", + "minItems": 1, + "description": "Array of event names where the command should be active (requires at least one). Use GitHub Actions event names.", + "items": { + "type": "string", + "description": "GitHub Actions event name.", + "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"] + } + } + ] + } + }, + "additionalProperties": false + } + ] + }, + "push": { + "description": "Push event trigger that runs the workflow when code is pushed to the repository", + "type": "object", + "additionalProperties": false, + "properties": { + "branches": { + "type": "array", + "$comment": "Mutually exclusive with branches-ignore. GitHub Actions requires only one to be specified.", + "description": "Branches to filter on", + "items": { + "type": "string" + } + }, + "branches-ignore": { + "type": "array", + "$comment": "Mutually exclusive with branches. GitHub Actions requires only one to be specified.", + "description": "Branches to ignore", + "items": { + "type": "string" + } + }, + "paths": { + "type": "array", + "$comment": "Mutually exclusive with paths-ignore. GitHub Actions requires only one to be specified.", + "description": "Paths to filter on", + "items": { + "type": "string" + } + }, + "paths-ignore": { + "type": "array", + "$comment": "Mutually exclusive with paths. GitHub Actions requires only one to be specified.", + "description": "Paths to ignore", + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "description": "List of git tag names or patterns to include for push events (supports wildcards)", + "items": { + "type": "string" + } + }, + "tags-ignore": { + "type": "array", + "description": "List of git tag names or patterns to exclude from push events (supports wildcards)", + "items": { + "type": "string" + } + } + } + }, + "pull_request": { + "description": "Pull request event trigger that runs the workflow when pull requests are created, updated, or closed", + "type": "object", + "properties": { + "types": { + "type": "array", + "description": "Pull request event types to trigger on. Note: 'converted_to_draft' and 'ready_for_review' represent state transitions (events) rather than states. While technically valid to listen for both, consider if you need to handle both transitions or just one.", + "$comment": "converted_to_draft and ready_for_review are logically opposite state transitions. Using both may indicate unclear intent.", + "items": { + "type": "string", + "enum": [ + "assigned", + "unassigned", + "labeled", + "unlabeled", + "opened", + "edited", + "closed", + "reopened", + "synchronize", + "converted_to_draft", + "locked", + "unlocked", + "enqueued", + "dequeued", + "milestoned", + "demilestoned", + "ready_for_review", + "review_requested", + "review_request_removed", + "auto_merge_enabled", + "auto_merge_disabled" + ] + } + }, + "branches": { + "type": "array", + "$comment": "Mutually exclusive with branches-ignore. GitHub Actions requires only one to be specified.", + "description": "Branches to filter on", + "items": { + "type": "string" + } + }, + "branches-ignore": { + "type": "array", + "$comment": "Mutually exclusive with branches. GitHub Actions requires only one to be specified.", + "description": "Branches to ignore", + "items": { + "type": "string" + } + }, + "paths": { + "type": "array", + "$comment": "Mutually exclusive with paths-ignore. GitHub Actions requires only one to be specified.", + "description": "Paths to filter on", + "items": { + "type": "string" + } + }, + "paths-ignore": { + "type": "array", + "$comment": "Mutually exclusive with paths. GitHub Actions requires only one to be specified.", + "description": "Paths to ignore", + "items": { + "type": "string" + } + }, + "draft": { + "type": "boolean", + "description": "Filter by draft pull request state. Set to false to exclude draft PRs, true to include only drafts, or omit to include both" + }, + "forks": { + "oneOf": [ + { + "type": "string", + "description": "Single fork pattern (e.g., '*' for all forks, 'org/*' for org glob, 'org/repo' for exact match)" + }, + { + "type": "array", + "description": "List of allowed fork repositories with glob support (e.g., 'org/repo', 'org/*', '*' for all forks)", + "items": { + "type": "string", + "description": "Repository pattern with optional glob support" + } + } + ] + }, + "names": { + "oneOf": [ + { + "type": "string", + "description": "Single label name to filter labeled/unlabeled events (e.g., 'bug')" + }, + { + "type": "array", + "description": "List of label names to filter labeled/unlabeled events. Only applies when 'labeled' or 'unlabeled' is in the types array", + "items": { + "type": "string", + "description": "Label name" + } + } + ] + } + }, + "additionalProperties": false + }, + "issues": { + "description": "Issues event trigger that runs the workflow when repository issues are created, updated, or managed", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of issue events", + "items": { + "type": "string", + "enum": ["opened", "edited", "deleted", "transferred", "pinned", "unpinned", "closed", "reopened", "assigned", "unassigned", "labeled", "unlabeled", "locked", "unlocked", "milestoned", "demilestoned", "typed", "untyped"] + } + }, + "names": { + "oneOf": [ + { + "type": "string", + "description": "Single label name to filter labeled/unlabeled events (e.g., 'bug')" + }, + { + "type": "array", + "description": "List of label names to filter labeled/unlabeled events. Only applies when 'labeled' or 'unlabeled' is in the types array", + "items": { + "type": "string", + "description": "Label name" + } + } + ] + }, + "lock-for-agent": { + "type": "boolean", + "description": "Whether to lock the issue for the agent when the workflow runs (prevents concurrent modifications)" + } + } + }, + "issue_comment": { + "description": "Issue comment event trigger", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of issue comment events", + "items": { + "type": "string", + "enum": ["created", "edited", "deleted"] + } + }, + "lock-for-agent": { + "type": "boolean", + "description": "Whether to lock the parent issue for the agent when the workflow runs (prevents concurrent modifications)" + } + } + }, + "discussion": { + "description": "Discussion event trigger that runs the workflow when repository discussions are created, updated, or managed", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of discussion events", + "items": { + "type": "string", + "enum": ["created", "edited", "deleted", "transferred", "pinned", "unpinned", "labeled", "unlabeled", "locked", "unlocked", "category_changed", "answered", "unanswered"] + } + } + } + }, + "discussion_comment": { + "description": "Discussion comment event trigger that runs the workflow when comments on discussions are created, updated, or deleted", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of discussion comment events", + "items": { + "type": "string", + "enum": ["created", "edited", "deleted"] + } + } + } + }, + "schedule": { + "description": "Scheduled trigger events using human-friendly format or standard cron expressions. Supports shorthand string notation (e.g., 'daily at 3pm') or array of schedule objects. Human-friendly formats are automatically converted to cron expressions with the original format preserved as comments in the generated workflow.", + "oneOf": [ + { + "type": "string", + "minLength": 1, + "description": "Shorthand schedule string using human-friendly format. Examples: 'daily at 02:00', 'daily at 3pm', 'daily at 6am', 'weekly on monday at 06:30', 'weekly on friday at 5pm', 'monthly on 15 at 09:00', 'monthly on 15 at 9am', 'every 10 minutes', 'every 2h', 'every 1d', 'daily at 02:00 utc+9', 'daily at 3pm utc+9'. Supports 12-hour format (1am-12am, 1pm-12pm), 24-hour format (HH:MM), midnight, noon. Minimum interval is 5 minutes. Converted to standard cron expression automatically." + }, + { + "type": "array", + "minItems": 1, + "description": "Array of schedule objects with cron expressions (standard or human-friendly format)", + "items": { + "type": "object", + "properties": { + "cron": { + "type": "string", + "description": "Cron expression using standard format (e.g., '0 9 * * 1') or human-friendly format (e.g., 'daily at 02:00', 'daily at 3pm', 'daily at 6am', 'weekly on monday', 'weekly on friday at 5pm', 'every 10 minutes', 'every 2h', 'daily at 02:00 utc+9', 'daily at 3pm utc+9'). Human-friendly formats support: daily/weekly/monthly schedules with optional time, interval schedules (minimum 5 minutes), short duration units (m/h/d/w/mo), 12-hour time format (Npm/Nam where N is 1-12), and UTC timezone offsets (utc+N or utc+HH:MM)." + } + }, + "required": ["cron"], + "additionalProperties": false + } + } + ] + }, + "workflow_dispatch": { + "description": "Manual workflow dispatch trigger", + "oneOf": [ + { + "type": "null", + "description": "Simple workflow dispatch trigger" + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "inputs": { + "type": "object", + "description": "Input parameters for manual dispatch", + "maxProperties": 25, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "description": { + "type": "string", + "description": "Input description" + }, + "required": { + "type": "boolean", + "description": "Whether input is required" + }, + "default": { + "type": "string", + "description": "Default value" + }, + "type": { + "type": "string", + "enum": ["string", "choice", "boolean"], + "description": "Input type" + }, + "options": { + "type": "array", + "description": "Options for choice type", + "items": { + "type": "string" + } + } + } + } + } + } + } + ] + }, + "workflow_run": { + "description": "Workflow run trigger", + "type": "object", + "additionalProperties": false, + "properties": { + "workflows": { + "type": "array", + "description": "List of workflows to trigger on", + "items": { + "type": "string" + } + }, + "types": { + "type": "array", + "description": "Types of workflow run events", + "items": { + "type": "string", + "enum": ["completed", "requested", "in_progress"] + } + }, + "branches": { + "type": "array", + "$comment": "Mutually exclusive with branches-ignore. GitHub Actions requires only one to be specified.", + "description": "Branches to filter on", + "items": { + "type": "string" + } + }, + "branches-ignore": { + "type": "array", + "$comment": "Mutually exclusive with branches. GitHub Actions requires only one to be specified.", + "description": "Branches to ignore", + "items": { + "type": "string" + } + } + } + }, + "release": { + "description": "Release event trigger", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of release events", + "items": { + "type": "string", + "enum": ["published", "unpublished", "created", "edited", "deleted", "prereleased", "released"] + } + } + } + }, + "pull_request_review_comment": { + "description": "Pull request review comment event trigger", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of pull request review comment events", + "items": { + "type": "string", + "enum": ["created", "edited", "deleted"] + } + } + } + }, + "branch_protection_rule": { + "description": "Branch protection rule event trigger that runs when branch protection rules are changed", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of branch protection rule events", + "items": { + "type": "string", + "enum": ["created", "edited", "deleted"] + } + } + } + }, + "check_run": { + "description": "Check run event trigger that runs when a check run is created, rerequested, completed, or has a requested action", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of check run events", + "items": { + "type": "string", + "enum": ["created", "rerequested", "completed", "requested_action"] + } + } + } + }, + "check_suite": { + "description": "Check suite event trigger that runs when check suite activity occurs", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of check suite events", + "items": { + "type": "string", + "enum": ["completed"] + } + } + } + }, + "create": { + "description": "Create event trigger that runs when a Git reference (branch or tag) is created", + "oneOf": [ + { + "type": "null", + "description": "Simple create event trigger" + }, + { + "type": "object", + "additionalProperties": false + } + ] + }, + "delete": { + "description": "Delete event trigger that runs when a Git reference (branch or tag) is deleted", + "oneOf": [ + { + "type": "null", + "description": "Simple delete event trigger" + }, + { + "type": "object", + "additionalProperties": false + } + ] + }, + "deployment": { + "description": "Deployment event trigger that runs when a deployment is created", + "oneOf": [ + { + "type": "null", + "description": "Simple deployment event trigger" + }, + { + "type": "object", + "additionalProperties": false + } + ] + }, + "deployment_status": { + "description": "Deployment status event trigger that runs when a deployment status is updated", + "oneOf": [ + { + "type": "null", + "description": "Simple deployment status event trigger" + }, + { + "type": "object", + "additionalProperties": false + } + ] + }, + "fork": { + "description": "Fork event trigger that runs when someone forks the repository", + "oneOf": [ + { + "type": "null", + "description": "Simple fork event trigger" + }, + { + "type": "object", + "additionalProperties": false + } + ] + }, + "gollum": { + "description": "Gollum event trigger that runs when someone creates or updates a Wiki page", + "oneOf": [ + { + "type": "null", + "description": "Simple gollum event trigger" + }, + { + "type": "object", + "additionalProperties": false + } + ] + }, + "label": { + "description": "Label event trigger that runs when a label is created, edited, or deleted", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of label events", + "items": { + "type": "string", + "enum": ["created", "edited", "deleted"] + } + } + } + }, + "merge_group": { + "description": "Merge group event trigger that runs when a pull request is added to a merge queue", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of merge group events", + "items": { + "type": "string", + "enum": ["checks_requested"] + } + } + } + }, + "milestone": { + "description": "Milestone event trigger that runs when a milestone is created, closed, opened, edited, or deleted", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of milestone events", + "items": { + "type": "string", + "enum": ["created", "closed", "opened", "edited", "deleted"] + } + } + } + }, + "page_build": { + "description": "Page build event trigger that runs when someone pushes to a GitHub Pages publishing source branch", + "oneOf": [ + { + "type": "null", + "description": "Simple page build event trigger" + }, + { + "type": "object", + "additionalProperties": false + } + ] + }, + "public": { + "description": "Public event trigger that runs when a repository changes from private to public", + "oneOf": [ + { + "type": "null", + "description": "Simple public event trigger" + }, + { + "type": "object", + "additionalProperties": false + } + ] + }, + "pull_request_target": { + "description": "Pull request target event trigger that runs in the context of the base repository (secure for fork PRs)", + "type": "object", + "properties": { + "types": { + "type": "array", + "description": "List of pull request target event types to trigger on", + "items": { + "type": "string", + "enum": [ + "assigned", + "unassigned", + "labeled", + "unlabeled", + "opened", + "edited", + "closed", + "reopened", + "synchronize", + "converted_to_draft", + "locked", + "unlocked", + "enqueued", + "dequeued", + "review_requested", + "review_request_removed", + "auto_merge_enabled", + "auto_merge_disabled" + ] + } + }, + "branches": { + "type": "array", + "$comment": "Mutually exclusive with branches-ignore. GitHub Actions requires only one to be specified.", + "description": "Branches to filter on", + "items": { + "type": "string" + } + }, + "branches-ignore": { + "type": "array", + "$comment": "Mutually exclusive with branches. GitHub Actions requires only one to be specified.", + "description": "Branches to ignore", + "items": { + "type": "string" + } + }, + "paths": { + "type": "array", + "$comment": "Mutually exclusive with paths-ignore. GitHub Actions requires only one to be specified.", + "description": "Paths to filter on", + "items": { + "type": "string" + } + }, + "paths-ignore": { + "type": "array", + "$comment": "Mutually exclusive with paths. GitHub Actions requires only one to be specified.", + "description": "Paths to ignore", + "items": { + "type": "string" + } + }, + "draft": { + "type": "boolean", + "description": "Filter by draft pull request state" + }, + "forks": { + "oneOf": [ + { + "type": "string", + "description": "Single fork pattern" + }, + { + "type": "array", + "description": "List of allowed fork repositories with glob support", + "items": { + "type": "string" + } + } + ] + } + }, + "additionalProperties": false + }, + "pull_request_review": { + "description": "Pull request review event trigger that runs when a pull request review is submitted, edited, or dismissed", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of pull request review events", + "items": { + "type": "string", + "enum": ["submitted", "edited", "dismissed"] + } + } + } + }, + "registry_package": { + "description": "Registry package event trigger that runs when a package is published or updated", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of registry package events", + "items": { + "type": "string", + "enum": ["published", "updated"] + } + } + } + }, + "repository_dispatch": { + "description": "Repository dispatch event trigger for custom webhook events", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Custom event types to trigger on", + "items": { + "type": "string" + } + } + } + }, + "status": { + "description": "Status event trigger that runs when the status of a Git commit changes", + "oneOf": [ + { + "type": "null", + "description": "Simple status event trigger" + }, + { + "type": "object", + "additionalProperties": false + } + ] + }, + "watch": { + "description": "Watch event trigger that runs when someone stars the repository", + "type": "object", + "additionalProperties": false, + "properties": { + "types": { + "type": "array", + "description": "Types of watch events", + "items": { + "type": "string", + "enum": ["started"] + } + } + } + }, + "workflow_call": { + "description": "Workflow call event trigger that allows this workflow to be called by another workflow", + "oneOf": [ + { + "type": "null", + "description": "Simple workflow call event trigger" + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "inputs": { + "type": "object", + "description": "Input parameters that can be passed to the workflow when it is called", + "additionalProperties": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "Description of the input parameter" + }, + "required": { + "type": "boolean", + "description": "Whether the input is required" + }, + "type": { + "type": "string", + "enum": ["string", "number", "boolean"], + "description": "Type of the input parameter" + }, + "default": { + "description": "Default value for the input parameter" + } + } + } + }, + "secrets": { + "type": "object", + "description": "Secrets that can be passed to the workflow when it is called", + "additionalProperties": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "Description of the secret" + }, + "required": { + "type": "boolean", + "description": "Whether the secret is required" + } + } + } + } + } + } + ] + }, + "stop-after": { + "type": "string", + "description": "Time when workflow should stop running. Supports multiple formats: absolute dates (YYYY-MM-DD HH:MM:SS, June 1 2025, 1st June 2025, 06/01/2025, etc.) or relative time deltas (+25h, +3d, +1d12h30m). Maximum values for time deltas: 12mo, 52w, 365d, 8760h (365 days). Note: Minute unit 'm' is not allowed for stop-after; minimum unit is hours 'h'." + }, + "skip-if-match": { + "oneOf": [ + { + "type": "string", + "description": "GitHub search query string to check before running workflow (implies max=1). If the search returns any results, the workflow will be skipped. Query is automatically scoped to the current repository. Example: 'is:issue is:open label:bug'" + }, + { + "type": "object", + "required": ["query"], + "properties": { + "query": { + "type": "string", + "description": "GitHub search query string to check before running workflow. Query is automatically scoped to the current repository." + }, + "max": { + "type": "integer", + "minimum": 1, + "description": "Maximum number of items that must be matched for the workflow to be skipped. Defaults to 1 if not specified." + } + }, + "additionalProperties": false, + "description": "Skip-if-match configuration object with query and maximum match count" + } + ], + "description": "Conditionally skip workflow execution when a GitHub search query has matches. Can be a string (query only, implies max=1) or an object with 'query' and optional 'max' fields." + }, + "manual-approval": { + "type": "string", + "description": "Environment name that requires manual approval before the workflow can run. Must match a valid environment configured in the repository settings." + }, + "reaction": { + "oneOf": [ + { + "type": "string", + "enum": ["+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", "eyes", "none"] + }, + { + "type": "integer", + "enum": [1, -1], + "description": "YAML parses +1 and -1 without quotes as integers. These are converted to +1 and -1 strings respectively." + } + ], + "default": "eyes", + "description": "AI reaction to add/remove on triggering item (one of: +1, -1, laugh, confused, heart, hooray, rocket, eyes, none). Use 'none' to disable reactions. Defaults to 'eyes' if not specified.", + "examples": ["eyes", "rocket", "+1", 1, -1, "none"] + } + }, + "additionalProperties": false, + "examples": [ + { + "schedule": [ + { + "cron": "0 0 * * *" + } + ], + "workflow_dispatch": null + }, + { + "command": { + "name": "mergefest", + "events": ["pull_request_comment"] + } + }, + { + "workflow_run": { + "workflows": ["Dev"], + "types": ["completed"], + "branches": ["copilot/**"] + } + }, + { + "pull_request": { + "types": ["ready_for_review"] + }, + "workflow_dispatch": null + }, + { + "push": { + "branches": ["main"] + } + } + ] + } + ] + }, + "permissions": { + "description": "GitHub token permissions for the workflow. Controls what the GITHUB_TOKEN can access during execution. Use the principle of least privilege - only grant the minimum permissions needed.", + "examples": [ + "read-all", + { + "contents": "read", + "actions": "read", + "pull-requests": "read" + }, + { + "contents": "read", + "actions": "read" + }, + { + "all": "read" + } + ], + "oneOf": [ + { + "type": "string", + "enum": ["read-all", "write-all", "read", "write"], + "description": "Simple permissions string: 'read-all' (all read permissions), 'write-all' (all write permissions), 'read' or 'write' (basic level)" + }, + { + "type": "object", + "description": "Detailed permissions object with granular control over specific GitHub API scopes", + "additionalProperties": false, + "properties": { + "actions": { + "type": "string", + "enum": ["read", "write", "none"], + "description": "Permission for GitHub Actions workflows and runs (read: view workflows, write: manage workflows, none: no access)" + }, + "attestations": { + "type": "string", + "enum": ["read", "write", "none"], + "description": "Permission for artifact attestations (read: view attestations, write: create attestations, none: no access)" + }, + "checks": { + "type": "string", + "enum": ["read", "write", "none"], + "description": "Permission for repository checks and status checks (read: view checks, write: create/update checks, none: no access)" + }, + "contents": { + "type": "string", + "enum": ["read", "write", "none"], + "description": "Permission for repository contents (read: view files, write: modify files/branches, none: no access)" + }, + "deployments": { + "type": "string", + "enum": ["read", "write", "none"], + "description": "Permission for repository deployments (read: view deployments, write: create/update deployments, none: no access)" + }, + "discussions": { + "type": "string", + "enum": ["read", "write", "none"], + "description": "Permission for repository discussions (read: view discussions, write: create/update discussions, none: no access)" + }, + "id-token": { + "type": "string", + "enum": ["read", "write", "none"] + }, + "issues": { + "type": "string", + "enum": ["read", "write", "none"], + "description": "Permission for repository issues (read: view issues, write: create/update/close issues, none: no access)" + }, + "models": { + "type": "string", + "enum": ["read", "none"], + "description": "Permission for GitHub Copilot models (read: access AI models for agentic workflows, none: no access)" + }, + "metadata": { + "type": "string", + "enum": ["read", "write", "none"], + "description": "Permission for repository metadata (read: view repository information, write: update repository metadata, none: no access)" + }, + "packages": { + "type": "string", + "enum": ["read", "write", "none"] + }, + "pages": { + "type": "string", + "enum": ["read", "write", "none"] + }, + "pull-requests": { + "type": "string", + "enum": ["read", "write", "none"] + }, + "security-events": { + "type": "string", + "enum": ["read", "write", "none"] + }, + "statuses": { + "type": "string", + "enum": ["read", "write", "none"] + }, + "all": { + "type": "string", + "enum": ["read"], + "description": "Permission shorthand that applies read access to all permission scopes. Can be combined with specific write permissions to override individual scopes. 'write' is not allowed for all." + } + } + } + ] + }, + "run-name": { + "type": "string", + "description": "Custom name for workflow runs that appears in the GitHub Actions interface (supports GitHub expressions like ${{ github.event.issue.title }})", + "examples": ["Deploy to ${{ github.event.inputs.environment }}", "Build #${{ github.run_number }}"] + }, + "jobs": { + "type": "object", + "description": "Groups together all the jobs that run in the workflow", + "additionalProperties": { + "type": "object", + "description": "Job definition", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Name of the job" + }, + "runs-on": { + "oneOf": [ + { + "type": "string", + "description": "Runner type as string" + }, + { + "type": "array", + "description": "Runner type as array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "description": "Runner type as object", + "additionalProperties": false + } + ] + }, + "steps": { + "type": "array", + "description": "A job contains a sequence of tasks called steps. Steps can run commands, run setup tasks, or run an action in your repository, a public repository, or an action published in a Docker registry.", + "items": { + "type": "object", + "additionalProperties": false, + "oneOf": [ + { + "required": ["uses"] + }, + { + "required": ["run"] + } + ], + "properties": { + "id": { + "type": "string", + "description": "A unique identifier for the step. You can use the id to reference the step in contexts." + }, + "if": { + "description": "You can use the if conditional to prevent a step from running unless a condition is met. You can use any supported context and expression to create a conditional.", + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "name": { + "type": "string", + "description": "A name for your step to display on GitHub." + }, + "uses": { + "type": "string", + "description": "Selects an action to run as part of a step in your job. An action is a reusable unit of code." + }, + "run": { + "type": "string", + "description": "Runs command-line programs using the operating system's shell." + }, + "working-directory": { + "type": "string", + "description": "Working directory where to run the command." + }, + "shell": { + "type": "string", + "description": "Shell to use for running the command." + }, + "with": { + "type": "object", + "description": "A map of the input parameters defined by the action. Each input parameter is a key/value pair.", + "additionalProperties": true + }, + "env": { + "type": "object", + "description": "Sets environment variables for steps to use in the virtual environment.", + "additionalProperties": { + "type": "string" + } + }, + "continue-on-error": { + "description": "Prevents a job from failing when a step fails. Set to true to allow a job to pass when this step fails.", + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + }, + "timeout-minutes": { + "description": "The maximum number of minutes to run the step before killing the process.", + "oneOf": [ + { + "type": "number" + }, + { + "type": "string" + } + ] + } + } + } + }, + "if": { + "type": "string", + "description": "Conditional execution for the job" + }, + "needs": { + "oneOf": [ + { + "type": "string", + "description": "Single job dependency" + }, + { + "type": "array", + "description": "Multiple job dependencies", + "items": { + "type": "string" + } + } + ] + }, + "env": { + "type": "object", + "description": "Environment variables for the job", + "additionalProperties": { + "type": "string" + } + }, + "permissions": { + "$ref": "#/properties/permissions" + }, + "timeout-minutes": { + "type": "integer", + "description": "Job timeout in minutes" + }, + "strategy": { + "type": "object", + "description": "Matrix strategy for the job", + "additionalProperties": false + }, + "continue-on-error": { + "type": "boolean", + "description": "Continue workflow on job failure" + }, + "container": { + "type": "object", + "description": "Container to run the job in", + "additionalProperties": false + }, + "services": { + "type": "object", + "description": "Service containers for the job", + "additionalProperties": { + "type": "object", + "additionalProperties": false + } + }, + "outputs": { + "type": "object", + "description": "Job outputs", + "additionalProperties": { + "type": "string" + } + }, + "concurrency": { + "$ref": "#/properties/concurrency" + }, + "uses": { + "type": "string", + "description": "Path to a reusable workflow file to call (e.g., ./.github/workflows/reusable-workflow.yml)" + }, + "with": { + "type": "object", + "description": "Input parameters to pass to the reusable workflow", + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + } + }, + "secrets": { + "type": "object", + "description": "Secrets to pass to the reusable workflow. Values must be GitHub Actions expressions referencing secrets (e.g., ${{ secrets.MY_SECRET }} or ${{ secrets.SECRET1 || secrets.SECRET2 }}).", + "additionalProperties": { + "$ref": "#/$defs/github_token" + } + } + } + } + }, + "runs-on": { + "description": "Runner type for workflow execution (GitHub Actions standard field). Supports multiple forms: simple string for single runner label (e.g., 'ubuntu-latest'), array for runner selection with fallbacks, or object for GitHub-hosted runner groups with specific labels. For agentic workflows, runner selection matters when AI workloads require specific compute resources or when using self-hosted runners with specialized capabilities. Typically configured at the job level instead. See https://docs.github.com/en/actions/using-jobs/choosing-the-runner-for-a-job", + "oneOf": [ + { + "type": "string", + "description": "Simple runner label string. Use for standard GitHub-hosted runners (e.g., 'ubuntu-latest', 'windows-latest', 'macos-latest') or self-hosted runner labels. Most common form for agentic workflows." + }, + { + "type": "array", + "description": "Array of runner labels for selection with fallbacks. GitHub Actions will use the first available runner that matches any label in the array. Useful for high-availability setups or when multiple runner types are acceptable.", + "items": { + "type": "string" + } + }, + { + "type": "object", + "description": "Runner group configuration for GitHub-hosted runners. Use this form to target specific runner groups (e.g., larger runners with more CPU/memory) or self-hosted runner pools with specific label requirements. Agentic workflows may benefit from larger runners for complex AI processing tasks.", + "additionalProperties": false, + "properties": { + "group": { + "type": "string", + "description": "Runner group name for self-hosted runners or GitHub-hosted runner groups" + }, + "labels": { + "type": "array", + "description": "List of runner labels for self-hosted runners or GitHub-hosted runner selection", + "items": { + "type": "string" + } + } + } + } + ], + "examples": [ + "ubuntu-latest", + ["ubuntu-latest", "self-hosted"], + { + "group": "larger-runners", + "labels": ["ubuntu-latest-8-cores"] + } + ] + }, + "timeout-minutes": { + "type": "integer", + "description": "Workflow timeout in minutes (GitHub Actions standard field). Defaults to 20 minutes for agentic workflows. Has sensible defaults and can typically be omitted.", + "examples": [5, 10, 30] + }, + "timeout_minutes": { + "type": "integer", + "description": "Deprecated: Use 'timeout-minutes' instead. Workflow timeout in minutes. Defaults to 20 minutes for agentic workflows.", + "examples": [5, 10, 30], + "deprecated": true + }, + "concurrency": { + "description": "Concurrency control to limit concurrent workflow runs (GitHub Actions standard field). Supports two forms: simple string for basic group isolation, or object with cancel-in-progress option for advanced control. Agentic workflows enhance this with automatic per-engine concurrency policies (defaults to single job per engine across all workflows) and token-based rate limiting. Default behavior: workflows in the same group queue sequentially unless cancel-in-progress is true. See https://docs.github.com/en/actions/using-jobs/using-concurrency", + "oneOf": [ + { + "type": "string", + "description": "Simple concurrency group name to prevent multiple runs in the same group. Use expressions like '${{ github.workflow }}' for per-workflow isolation or '${{ github.ref }}' for per-branch isolation. Agentic workflows automatically generate enhanced concurrency policies using 'gh-aw-{engine-id}' as the default group to limit concurrent AI workloads across all workflows using the same engine.", + "examples": ["my-workflow-group", "workflow-${{ github.ref }}"] + }, + { + "type": "object", + "description": "Concurrency configuration object with group isolation and cancellation control. Use object form when you need fine-grained control over whether to cancel in-progress runs. For agentic workflows, this is useful to prevent multiple AI agents from running simultaneously and consuming excessive resources or API quotas.", + "additionalProperties": false, + "properties": { + "group": { + "type": "string", + "description": "Concurrency group name. Workflows in the same group cannot run simultaneously. Supports GitHub Actions expressions for dynamic group names based on branch, workflow, or other context." + }, + "cancel-in-progress": { + "type": "boolean", + "description": "Whether to cancel in-progress workflows in the same concurrency group when a new one starts. Default: false (queue new runs). Set to true for agentic workflows where only the latest run matters (e.g., PR analysis that becomes stale when new commits are pushed)." + } + }, + "required": ["group"], + "examples": [ + { + "group": "dev-workflow-${{ github.ref }}", + "cancel-in-progress": true + } + ] + } + ], + "examples": [ + "my-workflow-group", + "workflow-${{ github.ref }}", + { + "group": "agentic-analysis-${{ github.workflow }}", + "cancel-in-progress": false + }, + { + "group": "pr-review-${{ github.event.pull_request.number }}", + "cancel-in-progress": true + } + ] + }, + "env": { + "$comment": "See environment variable precedence documentation: https://githubnext.github.io/gh-aw/reference/environment-variables/", + "description": "Environment variables for the workflow", + "oneOf": [ + { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "examples": [ + { + "NODE_ENV": "production", + "API_KEY": "${{ secrets.API_KEY }}" + } + ] + }, + { + "type": "string" + } + ] + }, + "features": { + "description": "Feature flags to enable experimental or optional features in the workflow. Each feature is specified as a key with a boolean value.", + "type": "object", + "additionalProperties": { + "type": "boolean" + } + }, + "environment": { + "description": "Environment that the job references (for protected environments and deployments)", + "oneOf": [ + { + "type": "string", + "description": "Environment name as a string" + }, + { + "type": "object", + "description": "Environment object with name and optional URL", + "properties": { + "name": { + "type": "string", + "description": "The name of the environment configured in the repo" + }, + "url": { + "type": "string", + "description": "A deployment URL" + } + }, + "required": ["name"], + "additionalProperties": false + } + ] + }, + "container": { + "description": "Container to run the job steps in", + "oneOf": [ + { + "type": "string", + "description": "Docker image name (e.g., 'node:18', 'ubuntu:latest')" + }, + { + "type": "object", + "description": "Container configuration object", + "properties": { + "image": { + "type": "string", + "description": "The Docker image to use as the container" + }, + "credentials": { + "type": "object", + "description": "Credentials for private registries", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "additionalProperties": false + }, + "env": { + "type": "object", + "description": "Environment variables for the container", + "additionalProperties": { + "type": "string" + } + }, + "ports": { + "type": "array", + "description": "Ports to expose on the container", + "items": { + "oneOf": [ + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + "volumes": { + "type": "array", + "description": "Volumes for the container", + "items": { + "type": "string" + } + }, + "options": { + "type": "string", + "description": "Additional Docker container options" + } + }, + "required": ["image"], + "additionalProperties": false + } + ] + }, + "services": { + "description": "Service containers for the job", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "string", + "description": "Docker image name for the service" + }, + { + "type": "object", + "description": "Service container configuration", + "properties": { + "image": { + "type": "string", + "description": "The Docker image to use for the service" + }, + "credentials": { + "type": "object", + "description": "Credentials for private registries", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "additionalProperties": false + }, + "env": { + "type": "object", + "description": "Environment variables for the service", + "additionalProperties": { + "type": "string" + } + }, + "ports": { + "type": "array", + "description": "Ports to expose on the service", + "items": { + "oneOf": [ + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + "volumes": { + "type": "array", + "description": "Volumes for the service", + "items": { + "type": "string" + } + }, + "options": { + "type": "string", + "description": "Additional Docker container options" + } + }, + "required": ["image"], + "additionalProperties": false + } + ] + } + }, + "network": { + "description": "Network access control for AI engines using ecosystem identifiers and domain allowlists. Controls web fetch and search capabilities.", + "examples": [ + "defaults", + { + "allowed": ["defaults", "github"] + }, + { + "allowed": ["defaults", "python", "node", "*.example.com"] + }, + { + "allowed": ["api.openai.com", "*.github.com"], + "firewall": { + "version": "v1.0.0", + "log-level": "debug" + } + } + ], + "oneOf": [ + { + "type": "string", + "enum": ["defaults"], + "description": "Use default network permissions (basic infrastructure: certificates, JSON schema, Ubuntu, etc.)" + }, + { + "type": "object", + "description": "Custom network access configuration with ecosystem identifiers and specific domains", + "properties": { + "allowed": { + "type": "array", + "description": "List of allowed domains or ecosystem identifiers (e.g., 'defaults', 'python', 'node', '*.example.com')", + "items": { + "type": "string", + "description": "Domain name or ecosystem identifier (supports wildcards like '*.example.com' and ecosystem names like 'python', 'node')" + } + }, + "firewall": { + "description": "AWF (Agent Workflow Firewall) configuration for network egress control. Only supported for Copilot engine.", + "deprecated": true, + "x-deprecation-message": "Use 'sandbox.agent: false' instead to disable the firewall for the agent", + "oneOf": [ + { + "type": "null", + "description": "Enable AWF with default settings (equivalent to empty object)" + }, + { + "type": "boolean", + "description": "Enable (true) or explicitly disable (false) AWF firewall" + }, + { + "type": "string", + "enum": ["disable"], + "description": "Disable AWF firewall (triggers warning if allowed != *, error in strict mode if allowed is not * or engine does not support firewall)" + }, + { + "type": "object", + "description": "Custom AWF configuration with version and arguments", + "properties": { + "args": { + "type": "array", + "description": "Optional additional arguments to pass to AWF wrapper", + "items": { + "type": "string" + } + }, + "version": { + "type": ["string", "number"], + "description": "AWF version to use (empty = latest release). Can be a string (e.g., 'v1.0.0', 'latest') or number (e.g., 20, 3.11). Numeric values are automatically converted to strings at runtime.", + "examples": ["v1.0.0", "latest", 20, 3.11] + }, + "log-level": { + "type": "string", + "description": "AWF log level (default: info). Valid values: debug, info, warn, error", + "enum": ["debug", "info", "warn", "error"] + } + }, + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + ] + }, + "sandbox": { + "description": "Sandbox configuration for AI engines. Controls agent sandbox (AWF or Sandbox Runtime) and MCP gateway.", + "oneOf": [ + { + "type": "string", + "enum": ["default", "sandbox-runtime", "awf", "srt"], + "description": "Legacy string format for sandbox type: 'default' for no sandbox, 'sandbox-runtime' or 'srt' for Anthropic Sandbox Runtime, 'awf' for Agent Workflow Firewall" + }, + { + "type": "object", + "description": "Object format for full sandbox configuration with agent and mcp options", + "properties": { + "type": { + "type": "string", + "enum": ["default", "sandbox-runtime", "awf", "srt"], + "description": "Legacy sandbox type field (use agent instead)" + }, + "agent": { + "description": "Agent sandbox type: 'awf' uses AWF (Agent Workflow Firewall), 'srt' uses Anthropic Sandbox Runtime, or 'false' to disable firewall", + "oneOf": [ + { + "type": "boolean", + "enum": [false], + "description": "Set to false to disable the agent firewall" + }, + { + "type": "string", + "enum": ["awf", "srt"], + "description": "Sandbox type: 'awf' for Agent Workflow Firewall, 'srt' for Sandbox Runtime" + }, + { + "type": "object", + "description": "Custom sandbox runtime configuration", + "properties": { + "id": { + "type": "string", + "enum": ["awf", "srt"], + "description": "Agent identifier (replaces 'type' field in new format): 'awf' for Agent Workflow Firewall, 'srt' for Sandbox Runtime" + }, + "type": { + "type": "string", + "enum": ["awf", "srt"], + "description": "Legacy: Sandbox type to use (use 'id' instead)" + }, + "command": { + "type": "string", + "description": "Custom command to replace the default AWF or SRT installation. For AWF: 'docker run my-custom-awf-image'. For SRT: 'docker run my-custom-srt-wrapper'" + }, + "args": { + "type": "array", + "description": "Additional arguments to append to the command (applies to both AWF and SRT, for standard and custom commands)", + "items": { + "type": "string" + } + }, + "env": { + "type": "object", + "description": "Environment variables to set on the execution step (applies to both AWF and SRT)", + "additionalProperties": { + "type": "string" + } + }, + "mounts": { + "type": "array", + "description": "Container mounts to add when using AWF. Each mount is specified using Docker mount syntax: 'source:destination:mode' where mode can be 'ro' (read-only) or 'rw' (read-write). Example: '/host/path:/container/path:ro'", + "items": { + "type": "string", + "pattern": "^[^:]+:[^:]+:(ro|rw)$", + "description": "Mount specification in format 'source:destination:mode'" + }, + "examples": [["/host/data:/data:ro", "/usr/local/bin/custom-tool:/usr/local/bin/custom-tool:ro"]] + }, + "config": { + "type": "object", + "description": "Custom Sandbox Runtime configuration (only applies when type is 'srt'). Note: Network configuration is controlled by the top-level 'network' field, not here.", + "properties": { + "filesystem": { + "type": "object", + "properties": { + "denyRead": { + "type": "array", + "description": "List of paths to deny read access", + "items": { + "type": "string" + } + }, + "allowWrite": { + "type": "array", + "description": "List of paths to allow write access", + "items": { + "type": "string" + } + }, + "denyWrite": { + "type": "array", + "description": "List of paths to deny write access", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "ignoreViolations": { + "type": "object", + "description": "Map of command patterns to paths that should ignore violations", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "enableWeakerNestedSandbox": { + "type": "boolean", + "description": "Enable weaker nested sandbox mode (recommended: true for Docker access)" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "config": { + "type": "object", + "description": "Legacy custom Sandbox Runtime configuration (use agent.config instead). Note: Network configuration is controlled by the top-level 'network' field, not here.", + "properties": { + "filesystem": { + "type": "object", + "properties": { + "denyRead": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowWrite": { + "type": "array", + "items": { + "type": "string" + } + }, + "denyWrite": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "ignoreViolations": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "enableWeakerNestedSandbox": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "mcp": { + "description": "MCP Gateway configuration for routing MCP server calls through a unified HTTP gateway. Requires the 'mcp-gateway' feature flag to be enabled.", + "type": "object", + "properties": { + "container": { + "type": "string", + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9/:_.-]*$", + "description": "Container image for the MCP gateway executable" + }, + "version": { + "type": ["string", "number"], + "description": "Optional version/tag for the container image (e.g., 'latest', 'v1.0.0')", + "examples": ["latest", "v1.0.0"] + }, + "args": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Arguments for container execution" + }, + "entrypointArgs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Arguments to add after the container image (container entrypoint arguments)" + }, + "env": { + "type": "object", + "patternProperties": { + "^[A-Z_][A-Z0-9_]*$": { + "type": "string" + } + }, + "additionalProperties": false, + "description": "Environment variables for MCP gateway" + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535, + "default": 8080, + "description": "Port number for the MCP gateway HTTP server (default: 8080)" + }, + "api-key": { + "type": "string", + "description": "API key for authenticating with the MCP gateway (supports ${{ secrets.* }} syntax)" + } + }, + "required": ["container"], + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "examples": [ + "default", + "sandbox-runtime", + { + "agent": "awf" + }, + { + "agent": "srt" + }, + { + "agent": { + "type": "srt", + "config": { + "filesystem": { + "allowWrite": [".", "/tmp"] + } + } + } + }, + { + "mcp": { + "container": "ghcr.io/githubnext/mcp-gateway", + "port": 8080 + } + }, + { + "agent": "awf", + "mcp": { + "container": "ghcr.io/githubnext/mcp-gateway", + "port": 8080, + "api-key": "${{ secrets.MCP_GATEWAY_API_KEY }}" + } + } + ] + }, + "if": { + "type": "string", + "description": "Conditional execution expression", + "examples": ["${{ github.event.workflow_run.event == 'workflow_dispatch' }}", "${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}"] + }, + "steps": { + "description": "Custom workflow steps", + "oneOf": [ + { + "type": "object", + "additionalProperties": true + }, + { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": true + } + ] + }, + "examples": [ + [ + { + "name": "Download logs from last 24 hours", + "env": { + "GH_TOKEN": "${{ secrets.GITHUB_TOKEN }}" + }, + "run": "./gh-aw logs --start-date -1d -o /tmp/gh-aw/aw-mcp/logs" + } + ] + ] + } + ] + }, + "post-steps": { + "description": "Custom workflow steps to run after AI execution", + "oneOf": [ + { + "type": "object", + "additionalProperties": true + }, + { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": true + } + ] + }, + "examples": [ + [ + { + "name": "Verify Post-Steps Execution", + "run": "echo \"\u2705 Post-steps are executing correctly\"\necho \"This step runs after the AI agent completes\"\n" + }, + { + "name": "Upload Test Results", + "if": "always()", + "uses": "actions/upload-artifact@v4", + "with": { + "name": "post-steps-test-results", + "path": "/tmp/gh-aw/", + "retention-days": 1, + "if-no-files-found": "ignore" + } + } + ] + ] + } + ] + }, + "engine": { + "description": "AI engine configuration that specifies which AI processor interprets and executes the markdown content of the workflow. Defaults to 'copilot'.", + "default": "copilot", + "$ref": "#/$defs/engine_config" + }, + "mcp-servers": { + "type": "object", + "description": "MCP server definitions", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "oneOf": [ + { + "$ref": "#/$defs/stdio_mcp_tool" + }, + { + "$ref": "#/$defs/http_mcp_tool" + } + ] + } + }, + "additionalProperties": false + }, + "tools": { + "type": "object", + "description": "Tools and MCP (Model Context Protocol) servers available to the AI engine for GitHub API access, browser automation, file editing, and more", + "properties": { + "github": { + "description": "GitHub API tools for repository operations (issues, pull requests, content management)", + "oneOf": [ + { + "type": "null", + "description": "Empty GitHub tool configuration (enables all read-only GitHub API functions)" + }, + { + "type": "boolean", + "description": "Boolean to explicitly enable (true) or disable (false) the GitHub MCP server. When set to false, the GitHub MCP server is not mounted." + }, + { + "type": "string", + "description": "Simple GitHub tool configuration (enables all GitHub API functions)" + }, + { + "type": "object", + "description": "GitHub tools object configuration with restricted function access", + "properties": { + "allowed": { + "type": "array", + "description": "List of allowed GitHub API functions (e.g., 'create_issue', 'update_issue', 'add_comment')", + "items": { + "type": "string" + } + }, + "mode": { + "type": "string", + "enum": ["local", "remote"], + "description": "MCP server mode: 'local' (Docker-based, default) or 'remote' (hosted at api.githubcopilot.com)" + }, + "version": { + "type": ["string", "number"], + "description": "Optional version specification for the GitHub MCP server (used with 'local' type). Can be a string (e.g., 'v1.0.0', 'latest') or number (e.g., 20, 3.11). Numeric values are automatically converted to strings at runtime.", + "examples": ["v1.0.0", "latest", 20, 3.11] + }, + "args": { + "type": "array", + "description": "Optional additional arguments to append to the generated MCP server command (used with 'local' type)", + "items": { + "type": "string" + } + }, + "read-only": { + "type": "boolean", + "description": "Enable read-only mode to restrict GitHub MCP server to read-only operations only" + }, + "lockdown": { + "type": "boolean", + "description": "Enable lockdown mode to limit content surfaced from public repositories (only items authored by users with push access). Default: false", + "default": false + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "Optional custom GitHub token (e.g., '${{ secrets.CUSTOM_PAT }}'). For 'remote' type, defaults to GH_AW_GITHUB_TOKEN if not specified." + }, + "toolsets": { + "type": "array", + "description": "Array of GitHub MCP server toolset names to enable specific groups of GitHub API functionalities", + "items": { + "type": "string", + "description": "Toolset name", + "enum": [ + "all", + "default", + "action-friendly", + "context", + "repos", + "issues", + "pull_requests", + "actions", + "code_security", + "dependabot", + "discussions", + "experiments", + "gists", + "labels", + "notifications", + "orgs", + "projects", + "search", + "secret_protection", + "security_advisories", + "stargazers", + "users" + ] + } + } + }, + "additionalProperties": false, + "examples": [ + { + "toolsets": ["pull_requests", "actions", "repos"] + }, + { + "allowed": ["search_pull_requests", "pull_request_read", "list_pull_requests", "get_file_contents", "list_commits", "get_commit"] + }, + { + "read-only": true + }, + { + "toolsets": ["pull_requests", "repos"] + } + ] + } + ], + "examples": [ + null, + { + "toolsets": ["pull_requests", "actions", "repos"] + }, + { + "allowed": ["search_pull_requests", "pull_request_read", "get_file_contents"] + }, + { + "read-only": true, + "toolsets": ["repos", "issues"] + }, + false + ] + }, + "bash": { + "description": "Bash shell command execution tool. Supports wildcards: '*' (all commands), 'command *' (command with any args, e.g., 'date *', 'echo *'). Default safe commands: echo, ls, pwd, cat, head, tail, grep, wc, sort, uniq, date.", + "oneOf": [ + { + "type": "null", + "description": "Enable bash tool with all shell commands allowed (security consideration: use restricted list in production)" + }, + { + "type": "boolean", + "description": "Enable bash tool - true allows all commands (equivalent to ['*']), false disables the tool" + }, + { + "type": "array", + "description": "List of allowed commands and patterns. Wildcards: '*' allows all commands, 'command *' allows command with any args (e.g., 'date *', 'echo *').", + "items": { + "type": "string", + "description": "Command or pattern: 'echo' (exact match), 'echo *' (command with any args)" + } + } + ], + "examples": [ + true, + ["git fetch", "git checkout", "git status", "git diff", "git log", "make recompile", "make fmt", "make lint", "make test-unit", "cat", "echo", "ls"], + ["echo", "ls", "cat"], + ["gh pr list *", "gh search prs *", "jq *"], + ["date *", "echo *", "cat", "ls"] + ] + }, + "web-fetch": { + "description": "Web content fetching tool for downloading web pages and API responses (subject to network permissions)", + "oneOf": [ + { + "type": "null", + "description": "Enable web fetch tool with default configuration" + }, + { + "type": "object", + "description": "Web fetch tool configuration object", + "additionalProperties": false + } + ] + }, + "web-search": { + "description": "Web search tool for performing internet searches and retrieving search results (subject to network permissions)", + "oneOf": [ + { + "type": "null", + "description": "Enable web search tool with default configuration" + }, + { + "type": "object", + "description": "Web search tool configuration object", + "additionalProperties": false + } + ] + }, + "edit": { + "description": "File editing tool for reading, creating, and modifying files in the repository", + "oneOf": [ + { + "type": "null", + "description": "Enable edit tool" + }, + { + "type": "object", + "description": "Edit tool configuration object", + "additionalProperties": false + } + ] + }, + "playwright": { + "description": "Playwright browser automation tool for web scraping, testing, and UI interactions in containerized browsers", + "oneOf": [ + { + "type": "null", + "description": "Enable Playwright tool with default settings (localhost access only for security)" + }, + { + "type": "object", + "description": "Playwright tool configuration with custom version and domain restrictions", + "properties": { + "version": { + "type": ["string", "number"], + "description": "Optional Playwright container version (e.g., 'v1.41.0', 1.41, 20). Numeric values are automatically converted to strings at runtime.", + "examples": ["v1.41.0", 1.41, 20] + }, + "allowed_domains": { + "description": "Domains allowed for Playwright browser network access. Defaults to localhost only for security.", + "oneOf": [ + { + "type": "array", + "description": "List of allowed domains or patterns (e.g., ['github.com', '*.example.com'])", + "items": { + "type": "string" + } + }, + { + "type": "string", + "description": "Single allowed domain (e.g., 'github.com')" + } + ] + }, + "args": { + "type": "array", + "description": "Optional additional arguments to append to the generated MCP server command", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "agentic-workflows": { + "description": "GitHub Agentic Workflows MCP server for workflow introspection and analysis. Provides tools for checking status, compiling workflows, downloading logs, and auditing runs.", + "oneOf": [ + { + "type": "boolean", + "description": "Enable agentic-workflows tool with default settings" + }, + { + "type": "null", + "description": "Enable agentic-workflows tool with default settings (same as true)" + } + ], + "examples": [true, null] + }, + "cache-memory": { + "description": "Cache memory MCP configuration for persistent memory storage", + "oneOf": [ + { + "type": "boolean", + "description": "Enable cache-memory with default settings" + }, + { + "type": "null", + "description": "Enable cache-memory with default settings (same as true)" + }, + { + "type": "object", + "description": "Cache-memory configuration object", + "properties": { + "key": { + "type": "string", + "description": "Custom cache key for memory MCP data (restore keys are auto-generated by splitting on '-')" + }, + "description": { + "type": "string", + "description": "Optional description for the cache that will be shown in the agent prompt" + }, + "retention-days": { + "type": "integer", + "minimum": 1, + "maximum": 90, + "description": "Number of days to retain uploaded artifacts (1-90 days, default: repository setting)" + }, + "restore-only": { + "type": "boolean", + "description": "If true, only restore the cache without saving it back. Uses actions/cache/restore instead of actions/cache. No artifact upload step will be generated." + } + }, + "additionalProperties": false, + "examples": [ + { + "key": "memory-audit-${{ github.workflow }}" + }, + { + "key": "memory-copilot-analysis", + "retention-days": 30 + } + ] + }, + { + "type": "array", + "description": "Array of cache-memory configurations for multiple caches", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Cache identifier for this cache entry" + }, + "key": { + "type": "string", + "description": "Cache key for this memory cache (supports GitHub Actions expressions like ${{ github.workflow }}, ${{ github.run_id }}). Restore keys are auto-generated by splitting on '-'." + }, + "description": { + "type": "string", + "description": "Optional description for this cache that will be shown in the agent prompt" + }, + "retention-days": { + "type": "integer", + "minimum": 1, + "maximum": 90, + "description": "Number of days to retain uploaded artifacts (1-90 days, default: repository setting)" + }, + "restore-only": { + "type": "boolean", + "description": "If true, only restore the cache without saving it back. Uses actions/cache/restore instead of actions/cache. No artifact upload step will be generated." + } + }, + "required": ["id", "key"], + "additionalProperties": false + }, + "minItems": 1, + "examples": [ + [ + { + "id": "default", + "key": "memory-default" + }, + { + "id": "session", + "key": "memory-session" + } + ] + ] + } + ], + "examples": [ + true, + null, + { + "key": "memory-audit-workflow" + }, + [ + { + "id": "default", + "key": "memory-default" + }, + { + "id": "logs", + "key": "memory-logs" + } + ] + ] + }, + "safety-prompt": { + "type": "boolean", + "description": "Enable or disable XPIA (Cross-Prompt Injection Attack) security warnings in the prompt. Defaults to true (enabled). Set to false to disable security warnings." + }, + "timeout": { + "type": "integer", + "minimum": 1, + "description": "Timeout in seconds for tool/MCP server operations. Applies to all tools and MCP servers if supported by the engine. Default varies by engine (Claude: 60s, Codex: 120s).", + "examples": [60, 120, 300] + }, + "startup-timeout": { + "type": "integer", + "minimum": 1, + "description": "Timeout in seconds for MCP server startup. Applies to MCP server initialization if supported by the engine. Default: 120 seconds." + }, + "serena": { + "description": "Serena MCP server for AI-powered code intelligence with language service integration", + "oneOf": [ + { + "type": "null", + "description": "Enable Serena with default settings" + }, + { + "type": "array", + "description": "Short syntax: array of language identifiers to enable (e.g., [\"go\", \"typescript\"])", + "items": { + "type": "string", + "enum": ["go", "typescript", "python", "java", "rust", "csharp"] + } + }, + { + "type": "object", + "description": "Serena configuration with custom version and language-specific settings", + "properties": { + "version": { + "type": ["string", "number"], + "description": "Optional Serena MCP version. Numeric values are automatically converted to strings at runtime.", + "examples": ["latest", "0.1.0", 1.0] + }, + "args": { + "type": "array", + "description": "Optional additional arguments to append to the generated MCP server command", + "items": { + "type": "string" + } + }, + "languages": { + "type": "object", + "description": "Language-specific configuration for Serena language services", + "properties": { + "go": { + "oneOf": [ + { + "type": "null", + "description": "Enable Go language service with default version" + }, + { + "type": "object", + "properties": { + "version": { + "type": ["string", "number"], + "description": "Go version (e.g., \"1.21\", 1.21)" + }, + "go-mod-file": { + "type": "string", + "description": "Path to go.mod file for Go version detection (e.g., \"go.mod\", \"backend/go.mod\")" + }, + "gopls-version": { + "type": "string", + "description": "Version of gopls to install (e.g., \"latest\", \"v0.14.2\")" + } + }, + "additionalProperties": false + } + ] + }, + "typescript": { + "oneOf": [ + { + "type": "null", + "description": "Enable TypeScript language service with default version" + }, + { + "type": "object", + "properties": { + "version": { + "type": ["string", "number"], + "description": "Node.js version for TypeScript (e.g., \"22\", 22)" + } + }, + "additionalProperties": false + } + ] + }, + "python": { + "oneOf": [ + { + "type": "null", + "description": "Enable Python language service with default version" + }, + { + "type": "object", + "properties": { + "version": { + "type": ["string", "number"], + "description": "Python version (e.g., \"3.12\", 3.12)" + } + }, + "additionalProperties": false + } + ] + }, + "java": { + "oneOf": [ + { + "type": "null", + "description": "Enable Java language service with default version" + }, + { + "type": "object", + "properties": { + "version": { + "type": ["string", "number"], + "description": "Java version (e.g., \"21\", 21)" + } + }, + "additionalProperties": false + } + ] + }, + "rust": { + "oneOf": [ + { + "type": "null", + "description": "Enable Rust language service with default version" + }, + { + "type": "object", + "properties": { + "version": { + "type": ["string", "number"], + "description": "Rust version (e.g., \"stable\", \"1.75\")" + } + }, + "additionalProperties": false + } + ] + }, + "csharp": { + "oneOf": [ + { + "type": "null", + "description": "Enable C# language service with default version" + }, + { + "type": "object", + "properties": { + "version": { + "type": ["string", "number"], + "description": ".NET version for C# (e.g., \"8.0\", 8.0)" + } + }, + "additionalProperties": false + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "repo-memory": { + "description": "Repo memory configuration for git-based persistent storage", + "oneOf": [ + { + "type": "boolean", + "description": "Enable repo-memory with default settings" + }, + { + "type": "null", + "description": "Enable repo-memory with default settings (same as true)" + }, + { + "type": "object", + "description": "Repo-memory configuration object", + "properties": { + "target-repo": { + "type": "string", + "description": "Target repository for memory storage (default: current repository). Format: owner/repo" + }, + "branch-name": { + "type": "string", + "description": "Git branch name for memory storage (default: memory/default)" + }, + "file-glob": { + "oneOf": [ + { + "type": "string", + "description": "Single file glob pattern for allowed files" + }, + { + "type": "array", + "description": "Array of file glob patterns for allowed files", + "items": { + "type": "string" + } + } + ] + }, + "max-file-size": { + "type": "integer", + "minimum": 1, + "maximum": 104857600, + "description": "Maximum size per file in bytes (default: 10240 = 10KB)" + }, + "max-file-count": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "description": "Maximum file count per commit (default: 100)" + }, + "description": { + "type": "string", + "description": "Optional description for the memory that will be shown in the agent prompt" + }, + "create-orphan": { + "type": "boolean", + "description": "Create orphaned branch if it doesn't exist (default: true)" + } + }, + "additionalProperties": false, + "examples": [ + { + "branch-name": "memory/session-state" + }, + { + "target-repo": "myorg/memory-repo", + "branch-name": "memory/agent-notes", + "max-file-size": 524288 + } + ] + }, + { + "type": "array", + "description": "Array of repo-memory configurations for multiple memory locations", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Memory identifier (required for array notation, default: 'default')" + }, + "target-repo": { + "type": "string", + "description": "Target repository for memory storage (default: current repository). Format: owner/repo" + }, + "branch-name": { + "type": "string", + "description": "Git branch name for memory storage (default: memory/{id})" + }, + "file-glob": { + "oneOf": [ + { + "type": "string", + "description": "Single file glob pattern for allowed files" + }, + { + "type": "array", + "description": "Array of file glob patterns for allowed files", + "items": { + "type": "string" + } + } + ] + }, + "max-file-size": { + "type": "integer", + "minimum": 1, + "maximum": 104857600, + "description": "Maximum size per file in bytes (default: 10240 = 10KB)" + }, + "max-file-count": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "description": "Maximum file count per commit (default: 100)" + }, + "description": { + "type": "string", + "description": "Optional description for this memory that will be shown in the agent prompt" + }, + "create-orphan": { + "type": "boolean", + "description": "Create orphaned branch if it doesn't exist (default: true)" + } + }, + "additionalProperties": false + }, + "minItems": 1, + "examples": [ + [ + { + "id": "default", + "branch-name": "memory/default" + }, + { + "id": "session", + "branch-name": "memory/session" + } + ] + ] + } + ], + "examples": [ + true, + null, + { + "branch-name": "memory/agent-state" + }, + [ + { + "id": "default", + "branch-name": "memory/default" + }, + { + "id": "logs", + "branch-name": "memory/logs", + "max-file-size": 524288 + } + ] + ] + } + }, + "additionalProperties": { + "description": "Simple tool string", + "type": "string" + } + }, + "command": { + "type": "string", + "description": "Command name for the workflow" + }, + "cache": { + "description": "Cache configuration for workflow (uses actions/cache syntax)", + "oneOf": [ + { + "type": "object", + "description": "Single cache configuration", + "properties": { + "key": { + "type": "string", + "description": "An explicit key for restoring and saving the cache" + }, + "path": { + "oneOf": [ + { + "type": "string", + "description": "A single path to cache" + }, + { + "type": "array", + "description": "Multiple paths to cache", + "items": { + "type": "string" + } + } + ] + }, + "restore-keys": { + "oneOf": [ + { + "type": "string", + "description": "A single restore key" + }, + { + "type": "array", + "description": "Multiple restore keys", + "items": { + "type": "string" + } + } + ] + }, + "upload-chunk-size": { + "type": "integer", + "description": "The chunk size used to split up large files during upload, in bytes" + }, + "fail-on-cache-miss": { + "type": "boolean", + "description": "Fail the workflow if cache entry is not found" + }, + "lookup-only": { + "type": "boolean", + "description": "If true, only checks if cache entry exists and skips download" + } + }, + "required": ["key", "path"], + "additionalProperties": false, + "examples": [ + { + "key": "node-modules-${{ hashFiles('package-lock.json') }}", + "path": "node_modules", + "restore-keys": ["node-modules-"] + }, + { + "key": "build-cache-${{ github.sha }}", + "path": ["dist", ".cache"], + "restore-keys": "build-cache-", + "fail-on-cache-miss": false + } + ] + }, + { + "type": "array", + "description": "Multiple cache configurations", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "An explicit key for restoring and saving the cache" + }, + "path": { + "oneOf": [ + { + "type": "string", + "description": "A single path to cache" + }, + { + "type": "array", + "description": "Multiple paths to cache", + "items": { + "type": "string" + } + } + ] + }, + "restore-keys": { + "oneOf": [ + { + "type": "string", + "description": "A single restore key" + }, + { + "type": "array", + "description": "Multiple restore keys", + "items": { + "type": "string" + } + } + ] + }, + "upload-chunk-size": { + "type": "integer", + "description": "The chunk size used to split up large files during upload, in bytes" + }, + "fail-on-cache-miss": { + "type": "boolean", + "description": "Fail the workflow if cache entry is not found" + }, + "lookup-only": { + "type": "boolean", + "description": "If true, only checks if cache entry exists and skips download" + } + }, + "required": ["key", "path"], + "additionalProperties": false + } + } + ] + }, + "safe-outputs": { + "type": "object", + "description": "Safe output processing configuration that automatically creates GitHub issues, comments, and pull requests from AI workflow output without requiring write permissions in the main job", + "$comment": "Required if workflow creates or modifies GitHub resources. Operations requiring safe-outputs: add-comment, add-labels, add-reviewer, assign-milestone, assign-to-agent, close-discussion, close-issue, close-pull-request, create-agent-task, create-code-scanning-alert, create-discussion, create-issue, create-pull-request, create-pull-request-review-comment, hide-comment, link-sub-issue, missing-tool, noop, push-to-pull-request-branch, threat-detection, update-discussion, update-issue, update-project, update-pull-request, update-release, upload-asset. See documentation for complete details.", + "properties": { + "allowed-domains": { + "type": "array", + "description": "List of allowed domains for URI filtering in AI workflow output. URLs from other domains will be replaced with '(redacted)' for security.", + "items": { + "type": "string" + } + }, + "create-issue": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for automatically creating GitHub issues from AI workflow output. The main job does not need 'issues: write' permission.", + "properties": { + "title-prefix": { + "type": "string", + "description": "Optional prefix to add to the beginning of the issue title (e.g., '[ai] ' or '[analysis] ')" + }, + "labels": { + "type": "array", + "description": "Optional list of labels to automatically attach to created issues (e.g., ['automation', 'ai-generated'])", + "items": { + "type": "string" + } + }, + "allowed-labels": { + "type": "array", + "description": "Optional list of allowed labels that can be used when creating issues. If omitted, any labels are allowed (including creating new ones). When specified, the agent can only use labels from this list.", + "items": { + "type": "string" + } + }, + "assignees": { + "oneOf": [ + { + "type": "string", + "description": "Single GitHub username to assign the created issue to (e.g., 'user1' or 'copilot'). Use 'copilot' to assign to GitHub Copilot using the @copilot special value." + }, + { + "type": "array", + "description": "List of GitHub usernames to assign the created issue to (e.g., ['user1', 'user2', 'copilot']). Use 'copilot' to assign to GitHub Copilot using the @copilot special value.", + "items": { + "type": "string" + } + } + ] + }, + "max": { + "type": "integer", + "description": "Maximum number of issues to create (default: 1)", + "minimum": 1, + "maximum": 100 + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository issue creation. Takes precedence over trial target repo settings." + }, + "allowed-repos": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of additional repositories in format 'owner/repo' that issues can be created in. When specified, the agent can use a 'repo' field in the output to specify which repository to create the issue in. The target repository (current or target-repo) is always implicitly allowed." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + }, + "expires": { + "oneOf": [ + { + "type": "integer", + "minimum": 1, + "description": "Number of days until expires" + }, + { + "type": "string", + "pattern": "^[0-9]+[hHdDwWmMyY]$", + "description": "Relative time (e.g., '20h', '7d', '2w', '1m', '1y')" + } + ], + "description": "Time until the issue expires and should be automatically closed. Supports integer (days) or relative time format. When set, a maintenance workflow will be generated." + } + }, + "additionalProperties": false, + "examples": [ + { + "title-prefix": "[ca] ", + "labels": ["automation", "dependencies"], + "assignees": "copilot" + }, + { + "title-prefix": "[duplicate-code] ", + "labels": ["code-quality", "automated-analysis"], + "assignees": "copilot" + }, + { + "allowed-repos": ["org/other-repo", "org/another-repo"], + "title-prefix": "[cross-repo] " + } + ] + }, + { + "type": "null", + "description": "Enable issue creation with default configuration" + } + ] + }, + "create-agent-task": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for creating GitHub Copilot agent tasks from agentic workflow output using gh agent-task CLI. The main job does not need write permissions.", + "properties": { + "base": { + "type": "string", + "description": "Base branch for the agent task pull request. Defaults to the current branch or repository default branch." + }, + "max": { + "type": "integer", + "description": "Maximum number of agent tasks to create (default: 1)", + "minimum": 1, + "maximum": 1 + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository agent task creation. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + }, + { + "type": "null", + "description": "Enable agent task creation with default configuration" + } + ] + }, + "update-project": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for managing GitHub Projects v2 boards. Smart tool that can add issue/PR items and update custom fields on existing items. By default it is update-only: if the project does not exist, the job fails with instructions to create it manually. To allow workflows to create missing projects, explicitly opt in via the agent output field create_if_missing=true (and/or provide a github-token override). NOTE: Projects v2 requires a Personal Access Token (PAT) or GitHub App token with appropriate permissions; the GITHUB_TOKEN cannot be used for Projects v2. Safe output items produced by the agent use type=update_project and may include: project (board name), content_type (issue|pull_request), content_number, fields, campaign_id, and create_if_missing.", + "properties": { + "max": { + "type": "integer", + "description": "Maximum number of project operations to perform (default: 10). Each operation may add a project item, or update its fields.", + "minimum": 1, + "maximum": 100 + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false, + "examples": [ + { + "max": 15 + }, + { + "github-token": "${{ secrets.PROJECT_GITHUB_TOKEN }}", + "max": 15 + } + ] + }, + { + "type": "null", + "description": "Enable project management with default configuration (max=10)" + } + ] + }, + "create-discussion": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for creating GitHub discussions from agentic workflow output", + "properties": { + "title-prefix": { + "type": "string", + "description": "Optional prefix for the discussion title" + }, + "category": { + "type": ["string", "number"], + "description": "Optional discussion category. Can be a category ID (string or numeric value), category name, or category slug/route. If not specified, uses the first available category. Matched first against category IDs, then against category names, then against category slugs. Numeric values are automatically converted to strings at runtime.", + "examples": ["General", "audits", 123456789] + }, + "labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Optional list of labels to attach to created discussions. Also used for matching when close-older-discussions is enabled - discussions must have ALL specified labels (AND logic)." + }, + "allowed-labels": { + "type": "array", + "description": "Optional list of allowed labels that can be used when creating discussions. If omitted, any labels are allowed (including creating new ones). When specified, the agent can only use labels from this list.", + "items": { + "type": "string" + } + }, + "max": { + "type": "integer", + "description": "Maximum number of discussions to create (default: 1)", + "minimum": 1, + "maximum": 100 + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository discussion creation. Takes precedence over trial target repo settings." + }, + "allowed-repos": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of additional repositories in format 'owner/repo' that discussions can be created in. When specified, the agent can use a 'repo' field in the output to specify which repository to create the discussion in. The target repository (current or target-repo) is always implicitly allowed." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + }, + "close-older-discussions": { + "type": "boolean", + "description": "When true, automatically close older discussions matching the same title prefix or labels as 'outdated' with a comment linking to the new discussion. Requires title-prefix or labels to be set. Maximum 10 discussions will be closed. Only runs if discussion creation succeeds.", + "default": false + }, + "expires": { + "oneOf": [ + { + "type": "integer", + "minimum": 1, + "description": "Number of days until expires" + }, + { + "type": "string", + "pattern": "^[0-9]+[hHdDwWmMyY]$", + "description": "Relative time (e.g., '20h', '7d', '2w', '1m', '1y')" + } + ], + "description": "Time until the discussion expires and should be automatically closed. Supports integer (days) or relative time format like '20h' (20 hours), '7d' (7 days), '2w' (2 weeks), '1m' (1 month), '1y' (1 year). When set, a maintenance workflow will be generated." + } + }, + "additionalProperties": false, + "examples": [ + { + "category": "audits" + }, + { + "title-prefix": "[copilot-agent-analysis] ", + "category": "audits", + "max": 1 + }, + { + "category": "General" + }, + { + "title-prefix": "[weekly-report] ", + "category": "reports", + "close-older-discussions": true + }, + { + "labels": ["weekly-report", "automation"], + "category": "reports", + "close-older-discussions": true + }, + { + "allowed-repos": ["org/other-repo"], + "category": "General" + } + ] + }, + { + "type": "null", + "description": "Enable discussion creation with default configuration" + } + ] + }, + "close-discussion": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for closing GitHub discussions with comment and resolution from agentic workflow output", + "properties": { + "required-labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Only close discussions that have all of these labels" + }, + "required-title-prefix": { + "type": "string", + "description": "Only close discussions with this title prefix" + }, + "required-category": { + "type": "string", + "description": "Only close discussions in this category" + }, + "target": { + "type": "string", + "description": "Target for closing: 'triggering' (default, current discussion), or '*' (any discussion with discussion_number field)" + }, + "max": { + "type": "integer", + "description": "Maximum number of discussions to close (default: 1)", + "minimum": 1, + "maximum": 100 + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository operations. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false, + "examples": [ + { + "required-category": "Ideas" + }, + { + "required-labels": ["resolved", "completed"], + "max": 1 + } + ] + }, + { + "type": "null", + "description": "Enable discussion closing with default configuration" + } + ] + }, + "update-discussion": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for updating GitHub discussions from agentic workflow output", + "properties": { + "target": { + "type": "string", + "description": "Target for updates: 'triggering' (default), '*' (any discussion), or explicit discussion number" + }, + "title": { + "type": "null", + "description": "Allow updating discussion title - presence of key indicates field can be updated" + }, + "body": { + "type": "null", + "description": "Allow updating discussion body - presence of key indicates field can be updated" + }, + "labels": { + "type": "null", + "description": "Allow updating discussion labels - presence of key indicates field can be updated" + }, + "allowed-labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Optional list of allowed labels. If omitted, any labels are allowed (including creating new ones)." + }, + "max": { + "type": "integer", + "description": "Maximum number of discussions to update (default: 1)", + "minimum": 1, + "maximum": 100 + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository discussion updates. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + }, + { + "type": "null", + "description": "Enable discussion updating with default configuration" + } + ] + }, + "close-issue": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for closing GitHub issues with comment from agentic workflow output", + "properties": { + "required-labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Only close issues that have all of these labels" + }, + "required-title-prefix": { + "type": "string", + "description": "Only close issues with this title prefix" + }, + "target": { + "type": "string", + "description": "Target for closing: 'triggering' (default, current issue), or '*' (any issue with issue_number field)" + }, + "max": { + "type": "integer", + "description": "Maximum number of issues to close (default: 1)", + "minimum": 1, + "maximum": 100 + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository operations. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false, + "examples": [ + { + "required-title-prefix": "[refactor] " + }, + { + "required-labels": ["automated", "stale"], + "max": 10 + } + ] + }, + { + "type": "null", + "description": "Enable issue closing with default configuration" + } + ] + }, + "close-pull-request": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for closing GitHub pull requests without merging, with comment from agentic workflow output", + "properties": { + "required-labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Only close pull requests that have any of these labels" + }, + "required-title-prefix": { + "type": "string", + "description": "Only close pull requests with this title prefix" + }, + "target": { + "type": "string", + "description": "Target for closing: 'triggering' (default, current PR), or '*' (any PR with pull_request_number field)" + }, + "max": { + "type": "integer", + "description": "Maximum number of pull requests to close (default: 1)", + "minimum": 1, + "maximum": 100 + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository operations. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false, + "examples": [ + { + "required-title-prefix": "[bot] " + }, + { + "required-labels": ["automated", "outdated"], + "max": 5 + } + ] + }, + { + "type": "null", + "description": "Enable pull request closing with default configuration" + } + ] + }, + "add-comment": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for automatically creating GitHub issue or pull request comments from AI workflow output. The main job does not need write permissions.", + "properties": { + "max": { + "type": "integer", + "description": "Maximum number of comments to create (default: 1)", + "minimum": 1, + "maximum": 100 + }, + "target": { + "type": "string", + "description": "Target for comments: 'triggering' (default), '*' (any issue), or explicit issue number" + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository comments. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + }, + "discussion": { + "type": "boolean", + "const": true, + "description": "Target discussion comments instead of issue/PR comments. Must be true if present." + }, + "hide-older-comments": { + "type": "boolean", + "description": "When true, minimizes/hides all previous comments from the same agentic workflow (identified by tracker-id) before creating the new comment. Default: false." + }, + "allowed-reasons": { + "type": "array", + "description": "List of allowed reasons for hiding older comments when hide-older-comments is enabled. Default: all reasons allowed (spam, abuse, off_topic, outdated, resolved).", + "items": { + "type": "string", + "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"] + } + } + }, + "additionalProperties": false, + "examples": [ + { + "max": 1, + "target": "*" + }, + { + "max": 3 + } + ] + }, + { + "type": "null", + "description": "Enable issue comment creation with default configuration" + } + ] + }, + "create-pull-request": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for creating GitHub pull requests from agentic workflow output. Note: The max parameter is not supported for pull requests - workflows are always limited to creating 1 pull request per run. This design decision prevents workflow runs from creating excessive PRs and maintains repository integrity.", + "properties": { + "title-prefix": { + "type": "string", + "description": "Optional prefix for the pull request title" + }, + "labels": { + "type": "array", + "description": "Optional list of labels to attach to the pull request", + "items": { + "type": "string" + } + }, + "allowed-labels": { + "type": "array", + "description": "Optional list of allowed labels that can be used when creating pull requests. If omitted, any labels are allowed (including creating new ones). When specified, the agent can only use labels from this list.", + "items": { + "type": "string" + } + }, + "reviewers": { + "oneOf": [ + { + "type": "string", + "description": "Single reviewer username to assign to the pull request. Use 'copilot' to request a code review from GitHub Copilot using the copilot-pull-request-reviewer[bot]." + }, + { + "type": "array", + "description": "List of reviewer usernames to assign to the pull request. Use 'copilot' to request a code review from GitHub Copilot using the copilot-pull-request-reviewer[bot].", + "items": { + "type": "string" + } + } + ], + "description": "Optional reviewer(s) to assign to the pull request. Accepts either a single string or an array of usernames. Use 'copilot' to request a code review from GitHub Copilot." + }, + "draft": { + "type": "boolean", + "description": "Whether to create pull request as draft (defaults to true)" + }, + "if-no-changes": { + "type": "string", + "enum": ["warn", "error", "ignore"], + "description": "Behavior when no changes to push: 'warn' (default - log warning but succeed), 'error' (fail the action), or 'ignore' (silent success)" + }, + "allow-empty": { + "type": "boolean", + "description": "When true, allows creating a pull request without any initial changes or git patch. This is useful for preparing a feature branch that an agent can push changes to later. The branch will be created from the base branch without applying any patch. Defaults to false." + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository pull request creation. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + }, + "expires": { + "oneOf": [ + { + "type": "integer", + "minimum": 1, + "description": "Number of days until expires" + }, + { + "type": "string", + "pattern": "^[0-9]+[hHdDwWmMyY]$", + "description": "Relative time (e.g., '20h', '7d', '2w', '1m', '1y')" + } + ], + "description": "Time until the pull request expires and should be automatically closed (only for same-repo PRs without target-repo). Supports integer (days) or relative time format." + } + }, + "additionalProperties": false, + "examples": [ + { + "title-prefix": "[docs] ", + "labels": ["documentation", "automation"], + "reviewers": "copilot", + "draft": false + }, + { + "title-prefix": "[security-fix] ", + "labels": ["security", "automated-fix"], + "reviewers": "copilot" + } + ] + }, + { + "type": "null", + "description": "Enable pull request creation with default configuration" + } + ] + }, + "create-pull-request-review-comment": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for creating GitHub pull request review comments from agentic workflow output", + "properties": { + "max": { + "type": "integer", + "description": "Maximum number of review comments to create (default: 10)", + "minimum": 1, + "maximum": 100 + }, + "side": { + "type": "string", + "description": "Side of the diff for comments: 'LEFT' or 'RIGHT' (default: 'RIGHT')", + "enum": ["LEFT", "RIGHT"] + }, + "target": { + "type": "string", + "description": "Target for review comments: 'triggering' (default, only on triggering PR), '*' (any PR, requires pull_request_number in agent output), or explicit PR number" + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository PR review comments. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + }, + { + "type": "null", + "description": "Enable PR review comment creation with default configuration" + } + ] + }, + "create-code-scanning-alert": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for creating repository security advisories (SARIF format) from agentic workflow output", + "properties": { + "max": { + "type": "integer", + "description": "Maximum number of security findings to include (default: unlimited)", + "minimum": 1 + }, + "driver": { + "type": "string", + "description": "Driver name for SARIF tool.driver.name field (default: 'GitHub Agentic Workflows Security Scanner')" + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + }, + { + "type": "null", + "description": "Enable code scanning alert creation with default configuration (unlimited findings)" + } + ] + }, + "add-labels": { + "oneOf": [ + { + "type": "null", + "description": "Null configuration allows any labels. Labels will be created if they don't already exist in the repository." + }, + { + "type": "object", + "description": "Configuration for adding labels to issues/PRs from agentic workflow output. Labels will be created if they don't already exist in the repository.", + "properties": { + "allowed": { + "type": "array", + "description": "Optional list of allowed labels that can be added. Labels will be created if they don't already exist in the repository. If omitted, any labels are allowed (including creating new ones).", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "max": { + "type": "integer", + "description": "Optional maximum number of labels to add (default: 3)", + "minimum": 1 + }, + "target": { + "type": "string", + "description": "Target for labels: 'triggering' (default), '*' (any issue/PR), or explicit issue/PR number" + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository label addition. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + } + ] + }, + "add-reviewer": { + "oneOf": [ + { + "type": "null", + "description": "Null configuration allows any reviewers" + }, + { + "type": "object", + "description": "Configuration for adding reviewers to pull requests from agentic workflow output", + "properties": { + "reviewers": { + "type": "array", + "description": "Optional list of allowed reviewers. If omitted, any reviewers are allowed.", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "max": { + "type": "integer", + "description": "Optional maximum number of reviewers to add (default: 3)", + "minimum": 1 + }, + "target": { + "type": "string", + "description": "Target for reviewers: 'triggering' (default), '*' (any PR), or explicit PR number" + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository reviewer addition. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + } + ] + }, + "assign-milestone": { + "oneOf": [ + { + "type": "null", + "description": "Null configuration allows assigning any milestones" + }, + { + "type": "object", + "description": "Configuration for assigning issues to milestones from agentic workflow output", + "properties": { + "allowed": { + "type": "array", + "description": "Optional list of allowed milestone titles that can be assigned. If omitted, any milestones are allowed.", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "max": { + "type": "integer", + "description": "Optional maximum number of milestone assignments (default: 1)", + "minimum": 1 + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository milestone assignment. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + } + ] + }, + "assign-to-agent": { + "oneOf": [ + { + "type": "null", + "description": "Null configuration uses default agent (copilot)" + }, + { + "type": "object", + "description": "Configuration for assigning GitHub Copilot agents to issues from agentic workflow output", + "properties": { + "name": { + "type": "string", + "description": "Default agent name to assign (default: 'copilot')" + }, + "max": { + "type": "integer", + "description": "Optional maximum number of agent assignments (default: 1)", + "minimum": 1 + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository agent assignment. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + } + ] + }, + "assign-to-user": { + "oneOf": [ + { + "type": "null", + "description": "Enable user assignment with default configuration" + }, + { + "type": "object", + "description": "Configuration for assigning users to issues from agentic workflow output", + "properties": { + "allowed": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Optional list of allowed usernames. If specified, only these users can be assigned." + }, + "max": { + "type": "integer", + "description": "Optional maximum number of user assignments (default: 1)", + "minimum": 1 + }, + "target": { + "type": ["string", "number"], + "description": "Target issue to assign users to. Use 'triggering' (default) for the triggering issue, '*' to allow any issue, or a specific issue number." + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository user assignment. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + } + ] + }, + "link-sub-issue": { + "oneOf": [ + { + "type": "null", + "description": "Enable sub-issue linking with default configuration" + }, + { + "type": "object", + "description": "Configuration for linking issues as sub-issues from agentic workflow output", + "properties": { + "max": { + "type": "integer", + "description": "Maximum number of sub-issue links to create (default: 5)", + "minimum": 1, + "maximum": 100 + }, + "parent-required-labels": { + "type": "array", + "description": "Optional list of labels that parent issues must have to be eligible for linking", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "parent-title-prefix": { + "type": "string", + "description": "Optional title prefix that parent issues must have to be eligible for linking" + }, + "sub-required-labels": { + "type": "array", + "description": "Optional list of labels that sub-issues must have to be eligible for linking", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "sub-title-prefix": { + "type": "string", + "description": "Optional title prefix that sub-issues must have to be eligible for linking" + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository sub-issue linking. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + } + ] + }, + "update-issue": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for updating GitHub issues from agentic workflow output", + "properties": { + "status": { + "type": "null", + "description": "Allow updating issue status (open/closed) - presence of key indicates field can be updated" + }, + "target": { + "type": "string", + "description": "Target for updates: 'triggering' (default), '*' (any issue), or explicit issue number" + }, + "title": { + "type": "null", + "description": "Allow updating issue title - presence of key indicates field can be updated" + }, + "body": { + "type": "null", + "description": "Allow updating issue body - presence of key indicates field can be updated" + }, + "max": { + "type": "integer", + "description": "Maximum number of issues to update (default: 1)", + "minimum": 1, + "maximum": 100 + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository issue updates. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + }, + { + "type": "null", + "description": "Enable issue updating with default configuration" + } + ] + }, + "update-pull-request": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for updating GitHub pull requests from agentic workflow output. Both title and body updates are enabled by default.", + "properties": { + "target": { + "type": "string", + "description": "Target for updates: 'triggering' (default), '*' (any PR), or explicit PR number" + }, + "title": { + "type": "boolean", + "description": "Allow updating pull request title - defaults to true, set to false to disable" + }, + "body": { + "type": "boolean", + "description": "Allow updating pull request body - defaults to true, set to false to disable" + }, + "max": { + "type": "integer", + "description": "Maximum number of pull requests to update (default: 1)", + "minimum": 1, + "maximum": 100 + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository pull request updates. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + }, + { + "type": "null", + "description": "Enable pull request updating with default configuration (title and body updates enabled)" + } + ] + }, + "push-to-pull-request-branch": { + "oneOf": [ + { + "type": "null", + "description": "Use default configuration (branch: 'triggering', if-no-changes: 'warn')" + }, + { + "type": "object", + "description": "Configuration for pushing changes to a specific branch from agentic workflow output", + "properties": { + "branch": { + "type": "string", + "description": "The branch to push changes to (defaults to 'triggering')" + }, + "target": { + "type": "string", + "description": "Target for push operations: 'triggering' (default), '*' (any pull request), or explicit pull request number" + }, + "title-prefix": { + "type": "string", + "description": "Required prefix for pull request title. Only pull requests with this prefix will be accepted." + }, + "labels": { + "type": "array", + "description": "Required labels for pull request validation. Only pull requests with all these labels will be accepted.", + "items": { + "type": "string" + } + }, + "if-no-changes": { + "type": "string", + "enum": ["warn", "error", "ignore"], + "description": "Behavior when no changes to push: 'warn' (default - log warning but succeed), 'error' (fail the action), or 'ignore' (silent success)" + }, + "commit-title-suffix": { + "type": "string", + "description": "Optional suffix to append to generated commit titles (e.g., ' [skip ci]' to prevent triggering CI on the commit)" + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + } + ] + }, + "hide-comment": { + "oneOf": [ + { + "type": "null", + "description": "Enable comment hiding with default configuration" + }, + { + "type": "object", + "description": "Configuration for hiding comments on GitHub issues, pull requests, or discussions from agentic workflow output", + "properties": { + "max": { + "type": "integer", + "description": "Maximum number of comments to hide (default: 5)", + "minimum": 1, + "maximum": 100 + }, + "target-repo": { + "type": "string", + "description": "Target repository in format 'owner/repo' for cross-repository comment hiding. Takes precedence over trial target repo settings." + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + }, + "allowed-reasons": { + "type": "array", + "description": "List of allowed reasons for hiding comments. Default: all reasons allowed (spam, abuse, off_topic, outdated, resolved).", + "items": { + "type": "string", + "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"] + } + } + }, + "additionalProperties": false + } + ] + }, + "missing-tool": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for reporting missing tools from agentic workflow output", + "properties": { + "max": { + "type": "integer", + "description": "Maximum number of missing tool reports (default: unlimited)", + "minimum": 1 + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + }, + { + "type": "null", + "description": "Enable missing tool reporting with default configuration" + }, + { + "type": "boolean", + "const": false, + "description": "Explicitly disable missing tool reporting (false). Missing tool reporting is enabled by default when safe-outputs is configured." + } + ] + }, + "noop": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for no-op safe output (logging only, no GitHub API calls). Always available as a fallback to ensure human-visible artifacts.", + "properties": { + "max": { + "type": "integer", + "description": "Maximum number of noop messages (default: 1)", + "minimum": 1, + "default": 1 + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + }, + { + "type": "null", + "description": "Enable noop output with default configuration (max: 1)" + }, + { + "type": "boolean", + "const": false, + "description": "Explicitly disable noop output (false). Noop is enabled by default when safe-outputs is configured." + } + ] + }, + "upload-assets": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for publishing assets to an orphaned git branch", + "properties": { + "branch": { + "type": "string", + "description": "Branch name (default: 'assets/${{ github.workflow }}')", + "default": "assets/${{ github.workflow }}" + }, + "max-size": { + "type": "integer", + "description": "Maximum file size in KB (default: 10240 = 10MB)", + "minimum": 1, + "maximum": 51200, + "default": 10240 + }, + "allowed-exts": { + "type": "array", + "description": "Allowed file extensions (default: common non-executable types)", + "items": { + "type": "string", + "pattern": "^\\.[a-zA-Z0-9]+$" + } + }, + "max": { + "type": "integer", + "description": "Maximum number of assets to upload (default: 10)", + "minimum": 1, + "maximum": 100 + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + }, + { + "type": "null", + "description": "Enable asset publishing with default configuration" + } + ] + }, + "update-release": { + "oneOf": [ + { + "type": "object", + "description": "Configuration for updating GitHub release descriptions", + "properties": { + "max": { + "type": "integer", + "description": "Maximum number of releases to update (default: 1)", + "minimum": 1, + "maximum": 10, + "default": 1 + }, + "target-repo": { + "type": "string", + "description": "Target repository for cross-repo release updates (format: owner/repo). If not specified, updates releases in the workflow's repository.", + "pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$" + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for this specific output type. Overrides global github-token if specified." + } + }, + "additionalProperties": false + }, + { + "type": "null", + "description": "Enable release updates with default configuration" + } + ] + }, + "staged": { + "type": "boolean", + "description": "If true, emit step summary messages instead of making GitHub API calls (preview mode)", + "examples": [true, false] + }, + "env": { + "type": "object", + "description": "Environment variables to pass to safe output jobs", + "patternProperties": { + "^[A-Za-z_][A-Za-z0-9_]*$": { + "type": "string", + "description": "Environment variable value, typically a secret reference like ${{ secrets.TOKEN_NAME }}" + } + }, + "additionalProperties": false + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for safe output jobs. Typically a secret reference like ${{ secrets.GITHUB_TOKEN }} or ${{ secrets.CUSTOM_PAT }}", + "examples": ["${{ secrets.GITHUB_TOKEN }}", "${{ secrets.CUSTOM_PAT }}", "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"] + }, + "app": { + "type": "object", + "description": "GitHub App credentials for minting installation access tokens. When configured, a token will be generated using the app credentials and used for all safe output operations.", + "properties": { + "app-id": { + "type": "string", + "description": "GitHub App ID. Should reference a variable (e.g., ${{ vars.APP_ID }}).", + "examples": ["${{ vars.APP_ID }}", "${{ secrets.APP_ID }}"] + }, + "private-key": { + "type": "string", + "description": "GitHub App private key. Should reference a secret (e.g., ${{ secrets.APP_PRIVATE_KEY }}).", + "examples": ["${{ secrets.APP_PRIVATE_KEY }}"] + }, + "owner": { + "type": "string", + "description": "Optional: The owner of the GitHub App installation. If empty, defaults to the current repository owner.", + "examples": ["my-organization", "${{ github.repository_owner }}"] + }, + "repositories": { + "type": "array", + "description": "Optional: Comma or newline-separated list of repositories to grant access to. If owner is set and repositories is empty, access will be scoped to all repositories in the provided repository owner's installation. If owner and repositories are empty, access will be scoped to only the current repository.", + "items": { + "type": "string" + }, + "examples": [["repo1", "repo2"], ["my-repo"]] + } + }, + "required": ["app-id", "private-key"], + "additionalProperties": false + }, + "max-patch-size": { + "type": "integer", + "description": "Maximum allowed size for git patches in kilobytes (KB). Defaults to 1024 KB (1 MB). If patch exceeds this size, the job will fail.", + "minimum": 1, + "maximum": 10240, + "default": 1024 + }, + "threat-detection": { + "oneOf": [ + { + "type": "boolean", + "description": "Enable or disable threat detection for safe outputs (defaults to true when safe-outputs are configured)" + }, + { + "type": "object", + "description": "Threat detection configuration object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether threat detection is enabled", + "default": true + }, + "prompt": { + "type": "string", + "description": "Additional custom prompt instructions to append to threat detection analysis" + }, + "engine": { + "description": "AI engine configuration specifically for threat detection (overrides main workflow engine). Set to false to disable AI-based threat detection. Supports same format as main engine field when not false.", + "oneOf": [ + { + "type": "boolean", + "const": false, + "description": "Disable AI engine for threat detection (only run custom steps)" + }, + { + "$ref": "#/$defs/engine_config" + } + ] + }, + "steps": { + "type": "array", + "description": "Array of extra job steps to run after detection", + "items": { + "$ref": "#/properties/githubActionsStep" + } + } + }, + "additionalProperties": false + } + ] + }, + "jobs": { + "type": "object", + "description": "Custom safe-output jobs that can be executed based on agentic workflow output. Job names containing dashes will be automatically normalized to underscores (e.g., 'send-notification' becomes 'send_notification').", + "patternProperties": { + "^[a-zA-Z_][a-zA-Z0-9_-]*$": { + "type": "object", + "description": "Custom safe-output job configuration. The job name will be normalized to use underscores instead of dashes.", + "properties": { + "name": { + "type": "string", + "description": "Display name for the job" + }, + "description": { + "type": "string", + "description": "Description of the safe-job (used in MCP tool registration)" + }, + "runs-on": { + "description": "Runner specification for this job", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "if": { + "type": "string", + "description": "Conditional expression for job execution" + }, + "needs": { + "description": "Job dependencies beyond the main job", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "env": { + "type": "object", + "description": "Job-specific environment variables", + "patternProperties": { + "^[A-Za-z_][A-Za-z0-9_]*$": { + "type": "string" + } + }, + "additionalProperties": false + }, + "permissions": { + "$ref": "#/properties/permissions" + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token for this specific job" + }, + "output": { + "type": "string", + "description": "Output configuration for the safe job" + }, + "inputs": { + "type": "object", + "description": "Input parameters for the safe job (workflow_dispatch syntax) - REQUIRED: at least one input must be defined", + "minProperties": 1, + "maxProperties": 25, + "patternProperties": { + "^[a-zA-Z_][a-zA-Z0-9_-]*$": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "Input parameter description" + }, + "required": { + "type": "boolean", + "description": "Whether this input is required", + "default": false + }, + "default": { + "type": "string", + "description": "Default value for the input" + }, + "type": { + "type": "string", + "enum": ["string", "boolean", "choice"], + "description": "Input parameter type", + "default": "string" + }, + "options": { + "type": "array", + "description": "Available options for choice type inputs", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "steps": { + "type": "array", + "description": "Custom steps to execute in the safe job", + "items": { + "$ref": "#/properties/githubActionsStep" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "messages": { + "type": "object", + "description": "Custom message templates for safe-output footer and notification messages. Available placeholders: {workflow_name} (workflow name), {run_url} (GitHub Actions run URL), {triggering_number} (issue/PR/discussion number), {workflow_source} (owner/repo/path@ref), {workflow_source_url} (GitHub URL to source), {operation} (safe-output operation name for staged mode).", + "properties": { + "footer": { + "type": "string", + "description": "Custom footer message template for AI-generated content. Available placeholders: {workflow_name}, {run_url}, {triggering_number}, {workflow_source}, {workflow_source_url}. Example: '> Generated by [{workflow_name}]({run_url})'", + "examples": ["> Generated by [{workflow_name}]({run_url})", "> AI output from [{workflow_name}]({run_url}) for #{triggering_number}"] + }, + "footer-install": { + "type": "string", + "description": "Custom installation instructions template appended to the footer. Available placeholders: {workflow_source}, {workflow_source_url}. Example: '> Install: `gh aw add {workflow_source}`'", + "examples": ["> Install: `gh aw add {workflow_source}`", "> [Add this workflow]({workflow_source_url})"] + }, + "staged-title": { + "type": "string", + "description": "Custom title template for staged mode preview. Available placeholders: {operation}. Example: '\ud83c\udfad Preview: {operation}'", + "examples": ["\ud83c\udfad Preview: {operation}", "## Staged Mode: {operation}"] + }, + "staged-description": { + "type": "string", + "description": "Custom description template for staged mode preview. Available placeholders: {operation}. Example: 'The following {operation} would occur if staged mode was disabled:'", + "examples": ["The following {operation} would occur if staged mode was disabled:"] + }, + "run-started": { + "type": "string", + "description": "Custom message template for workflow activation comment. Available placeholders: {workflow_name}, {run_url}, {event_type}. Default: 'Agentic [{workflow_name}]({run_url}) triggered by this {event_type}.'", + "examples": ["Agentic [{workflow_name}]({run_url}) triggered by this {event_type}.", "[{workflow_name}]({run_url}) started processing this {event_type}."] + }, + "run-success": { + "type": "string", + "description": "Custom message template for successful workflow completion. Available placeholders: {workflow_name}, {run_url}. Default: '\u2705 Agentic [{workflow_name}]({run_url}) completed successfully.'", + "examples": ["\u2705 Agentic [{workflow_name}]({run_url}) completed successfully.", "\u2705 [{workflow_name}]({run_url}) finished."] + }, + "run-failure": { + "type": "string", + "description": "Custom message template for failed workflow. Available placeholders: {workflow_name}, {run_url}, {status}. Default: '\u274c Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.'", + "examples": ["\u274c Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.", "\u274c [{workflow_name}]({run_url}) {status}."] + }, + "detection-failure": { + "type": "string", + "description": "Custom message template for detection job failure. Available placeholders: {workflow_name}, {run_url}. Default: '\u26a0\ufe0f Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.'", + "examples": ["\u26a0\ufe0f Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.", "\u26a0\ufe0f Detection job failed in [{workflow_name}]({run_url})."] + } + }, + "additionalProperties": false + }, + "mentions": { + "description": "Configuration for @mention filtering in safe outputs. Controls whether and how @mentions in AI-generated content are allowed or escaped.", + "oneOf": [ + { + "type": "boolean", + "description": "Simple boolean mode: false = always escape mentions, true = always allow mentions (error in strict mode)" + }, + { + "type": "object", + "description": "Advanced configuration for @mention filtering with fine-grained control", + "properties": { + "allow-team-members": { + "type": "boolean", + "description": "Allow mentions of repository team members (collaborators with any permission level, excluding bots). Default: true", + "default": true + }, + "allow-context": { + "type": "boolean", + "description": "Allow mentions inferred from event context (issue/PR authors, assignees, commenters). Default: true", + "default": true + }, + "allowed": { + "type": "array", + "description": "List of user/bot names always allowed to be mentioned. Bots are not allowed by default unless listed here.", + "items": { + "type": "string", + "minLength": 1 + } + }, + "max": { + "type": "integer", + "description": "Maximum number of mentions allowed per message. Default: 50", + "minimum": 1, + "default": 50 + } + }, + "additionalProperties": false + } + ] + }, + "runs-on": { + "type": "string", + "description": "Runner specification for all safe-outputs jobs (activation, create-issue, add-comment, etc.). Single runner label (e.g., 'ubuntu-slim', 'ubuntu-latest', 'windows-latest', 'self-hosted'). Defaults to 'ubuntu-slim'. See https://github.blog/changelog/2025-10-28-1-vcpu-linux-runner-now-available-in-github-actions-in-public-preview/" + } + }, + "additionalProperties": false + }, + "secret-masking": { + "type": "object", + "description": "Configuration for secret redaction behavior in workflow outputs and artifacts", + "properties": { + "steps": { + "type": "array", + "description": "Additional secret redaction steps to inject after the built-in secret redaction. Use this to mask secrets in generated files using custom patterns.", + "items": { + "$ref": "#/properties/githubActionsStep" + }, + "examples": [ + [ + { + "name": "Redact custom secrets", + "run": "find /tmp/gh-aw -type f -exec sed -i 's/password123/REDACTED/g' {} +" + } + ] + ] + } + }, + "additionalProperties": false + }, + "roles": { + "description": "Repository access roles required to trigger agentic workflows. Defaults to ['admin', 'maintainer', 'write'] for security. Use 'all' to allow any authenticated user (\u26a0\ufe0f security consideration).", + "oneOf": [ + { + "type": "string", + "enum": ["all"], + "description": "Allow any authenticated user to trigger the workflow (\u26a0\ufe0f disables permission checking entirely - use with caution)" + }, + { + "type": "array", + "description": "List of repository permission levels that can trigger the workflow. Permission checks are automatically applied to potentially unsafe triggers.", + "items": { + "type": "string", + "enum": ["admin", "maintainer", "maintain", "write", "triage"], + "description": "Repository permission level: 'admin' (full access), 'maintainer'/'maintain' (repository management), 'write' (push access), 'triage' (issue management)" + }, + "minItems": 1 + } + ] + }, + "bots": { + "type": "array", + "description": "Allow list of bot identifiers that can trigger the workflow even if they don't meet the required role permissions. When the actor is in this list, the bot must be active (installed) on the repository to trigger the workflow.", + "items": { + "type": "string", + "minLength": 1, + "description": "Bot identifier/name (e.g., 'dependabot[bot]', 'renovate[bot]', 'github-actions[bot]')" + } + }, + "githubActionsStep": { + "type": "object", + "description": "GitHub Actions workflow step", + "properties": { + "name": { + "type": "string", + "description": "A name for your step to display on GitHub" + }, + "id": { + "type": "string", + "description": "A unique identifier for the step" + }, + "if": { + "type": "string", + "description": "Conditional expression to determine if step should run" + }, + "uses": { + "type": "string", + "description": "Selects an action to run as part of a step in your job" + }, + "run": { + "type": "string", + "description": "Runs command-line programs using the operating system's shell" + }, + "with": { + "type": "object", + "description": "Input parameters defined by the action", + "additionalProperties": true + }, + "env": { + "type": "object", + "description": "Environment variables for the step", + "patternProperties": { + "^[A-Za-z_][A-Za-z0-9_]*$": { + "type": "string" + } + }, + "additionalProperties": false + }, + "continue-on-error": { + "type": "boolean", + "description": "Prevents a job from failing when a step fails" + }, + "timeout-minutes": { + "type": "number", + "description": "The maximum number of minutes to run the step before killing the process" + }, + "working-directory": { + "type": "string", + "description": "Working directory for the step" + }, + "shell": { + "type": "string", + "description": "Shell to use for the run command" + } + }, + "additionalProperties": false, + "anyOf": [ + { + "required": ["uses"] + }, + { + "required": ["run"] + } + ] + }, + "strict": { + "type": "boolean", + "default": true, + "description": "Enable strict mode validation for enhanced security and compliance. Strict mode enforces: (1) Write Permissions - refuses contents:write, issues:write, pull-requests:write; requires safe-outputs instead, (2) Network Configuration - requires explicit network configuration with no wildcard '*' in allowed domains, (3) Action Pinning - enforces actions pinned to commit SHAs instead of tags/branches, (4) MCP Network - requires network configuration for custom MCP servers with containers, (5) Deprecated Fields - refuses deprecated frontmatter fields. Can be enabled per-workflow via 'strict: true' in frontmatter, or disabled via 'strict: false'. CLI flag takes precedence over frontmatter (gh aw compile --strict enforces strict mode). Defaults to true. See: https://githubnext.github.io/gh-aw/reference/frontmatter/#strict-mode-strict", + "examples": [true, false] + }, + "safe-inputs": { + "type": "object", + "description": "Safe inputs configuration for defining custom lightweight MCP tools as JavaScript, shell scripts, or Python scripts. Tools are mounted in an MCP server and have access to secrets specified by the user. Only one of 'script' (JavaScript), 'run' (shell), or 'py' (Python) must be specified per tool.", + "patternProperties": { + "^([a-ln-z][a-z0-9_-]*|m[a-np-z][a-z0-9_-]*|mo[a-ce-z][a-z0-9_-]*|mod[a-df-z][a-z0-9_-]*|mode[a-z0-9_-]+)$": { + "type": "object", + "description": "Custom tool definition. The key is the tool name (lowercase alphanumeric with dashes/underscores).", + "required": ["description"], + "properties": { + "description": { + "type": "string", + "description": "Tool description that explains what the tool does. This is required and will be shown to the AI agent." + }, + "inputs": { + "type": "object", + "description": "Optional input parameters for the tool using workflow syntax. Each property defines an input with its type and description.", + "additionalProperties": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["string", "number", "boolean", "array", "object"], + "default": "string", + "description": "The JSON schema type of the input parameter." + }, + "description": { + "type": "string", + "description": "Description of the input parameter." + }, + "required": { + "type": "boolean", + "default": false, + "description": "Whether this input is required." + }, + "default": { + "description": "Default value for the input parameter." + } + }, + "additionalProperties": false + } + }, + "script": { + "type": "string", + "description": "JavaScript implementation (CommonJS format). The script receives input parameters as a JSON object and should return a result. Cannot be used together with 'run' or 'py'." + }, + "run": { + "type": "string", + "description": "Shell script implementation. The script receives input parameters as environment variables (JSON-encoded for complex types). Cannot be used together with 'script' or 'py'." + }, + "py": { + "type": "string", + "description": "Python script implementation. The script receives input parameters as environment variables (INPUT_* prefix, uppercased). Cannot be used together with 'script' or 'run'." + }, + "env": { + "type": "object", + "description": "Environment variables to pass to the tool, typically for secrets. Use ${{ secrets.NAME }} syntax.", + "additionalProperties": { + "type": "string" + }, + "examples": [ + { + "GH_TOKEN": "${{ secrets.GITHUB_TOKEN }}", + "API_KEY": "${{ secrets.MY_API_KEY }}" + } + ] + }, + "timeout": { + "type": "integer", + "description": "Timeout in seconds for tool execution. Default is 60 seconds. Applies to shell (run) and Python (py) tools.", + "default": 60, + "minimum": 1, + "examples": [30, 60, 120, 300] + } + }, + "additionalProperties": false, + "oneOf": [ + { + "required": ["script"], + "not": { + "anyOf": [ + { + "required": ["run"] + }, + { + "required": ["py"] + } + ] + } + }, + { + "required": ["run"], + "not": { + "anyOf": [ + { + "required": ["script"] + }, + { + "required": ["py"] + } + ] + } + }, + { + "required": ["py"], + "not": { + "anyOf": [ + { + "required": ["script"] + }, + { + "required": ["run"] + } + ] + } + } + ] + } + }, + "examples": [ + { + "search-issues": { + "description": "Search GitHub issues using the GitHub API", + "inputs": { + "query": { + "type": "string", + "description": "Search query for issues", + "required": true + }, + "limit": { + "type": "number", + "description": "Maximum number of results", + "default": 10 + } + }, + "script": "const { Octokit } = require('@octokit/rest');\nconst octokit = new Octokit({ auth: process.env.GH_TOKEN });\nconst result = await octokit.search.issuesAndPullRequests({ q: inputs.query, per_page: inputs.limit });\nreturn result.data.items;", + "env": { + "GH_TOKEN": "${{ secrets.GITHUB_TOKEN }}" + } + } + }, + { + "run-linter": { + "description": "Run a custom linter on the codebase", + "inputs": { + "path": { + "type": "string", + "description": "Path to lint", + "default": "." + } + }, + "run": "eslint $INPUT_PATH --format json", + "env": { + "INPUT_PATH": "${{ inputs.path }}" + } + } + } + ], + "properties": { + "mode": { + "type": "string", + "enum": ["http"], + "default": "http", + "description": "Deprecated: Transport mode for the safe-inputs MCP server. This field is ignored as only 'http' mode is supported. The server always starts as a separate step.", + "deprecated": true, + "x-deprecation-message": "The mode field is no longer used. Safe-inputs always uses HTTP transport." + } + } + }, + "runtimes": { + "type": "object", + "description": "Runtime environment version overrides. Allows customizing runtime versions (e.g., Node.js, Python) or defining new runtimes. Runtimes from imported shared workflows are also merged.", + "patternProperties": { + "^[a-z][a-z0-9-]*$": { + "type": "object", + "description": "Runtime configuration object identified by runtime ID (e.g., 'node', 'python', 'go')", + "properties": { + "version": { + "type": ["string", "number"], + "description": "Runtime version as a string (e.g., '22', '3.12', 'latest') or number (e.g., 22, 3.12). Numeric values are automatically converted to strings at runtime.", + "examples": ["22", "3.12", "latest", 22, 3.12] + }, + "action-repo": { + "type": "string", + "description": "GitHub Actions repository for setting up the runtime (e.g., 'actions/setup-node', 'custom/setup-runtime'). Overrides the default setup action." + }, + "action-version": { + "type": "string", + "description": "Version of the setup action to use (e.g., 'v4', 'v5'). Overrides the default action version." + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token expression to use for all steps that require GitHub authentication. Typically a secret reference like ${{ secrets.GITHUB_TOKEN }} or ${{ secrets.CUSTOM_PAT }}. If not specified, defaults to ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}. This value can be overridden by safe-outputs github-token or individual safe-output github-token fields." + } + }, + "additionalProperties": false, + "allOf": [ + { + "if": { + "properties": { + "on": { + "type": "object", + "anyOf": [ + { + "properties": { + "slash_command": { + "not": { + "type": "null" + } + } + }, + "required": ["slash_command"] + }, + { + "properties": { + "command": { + "not": { + "type": "null" + } + } + }, + "required": ["command"] + } + ] + } + } + }, + "then": { + "properties": { + "on": { + "not": { + "anyOf": [ + { + "properties": { + "issue_comment": { + "not": { + "type": "null" + } + } + }, + "required": ["issue_comment"] + }, + { + "properties": { + "pull_request_review_comment": { + "not": { + "type": "null" + } + } + }, + "required": ["pull_request_review_comment"] + } + ] + } + } + } + } + } + ], + "$defs": { + "engine_config": { + "examples": [ + "claude", + "copilot", + { + "id": "claude", + "model": "claude-3-5-sonnet-20241022", + "max-turns": 15 + }, + { + "id": "copilot", + "version": "beta" + }, + { + "id": "claude", + "concurrency": { + "group": "gh-aw-claude", + "cancel-in-progress": false + } + } + ], + "oneOf": [ + { + "type": "string", + "enum": ["claude", "codex", "copilot", "custom"], + "description": "Simple engine name: 'claude' (default, Claude Code), 'copilot' (GitHub Copilot CLI), 'codex' (OpenAI Codex CLI), or 'custom' (user-defined steps)" + }, + { + "type": "object", + "description": "Extended engine configuration object with advanced options for model selection, turn limiting, environment variables, and custom steps", + "properties": { + "id": { + "type": "string", + "enum": ["claude", "codex", "custom", "copilot"], + "description": "AI engine identifier: 'claude' (Claude Code), 'codex' (OpenAI Codex CLI), 'copilot' (GitHub Copilot CLI), or 'custom' (user-defined GitHub Actions steps)" + }, + "version": { + "type": ["string", "number"], + "description": "Optional version of the AI engine action (e.g., 'beta', 'stable', 20). Has sensible defaults and can typically be omitted. Numeric values are automatically converted to strings at runtime.", + "examples": ["beta", "stable", 20, 3.11] + }, + "model": { + "type": "string", + "description": "Optional specific LLM model to use (e.g., 'claude-3-5-sonnet-20241022', 'gpt-4'). Has sensible defaults and can typically be omitted." + }, + "max-turns": { + "type": "integer", + "description": "Maximum number of chat iterations per run. Helps prevent runaway loops and control costs. Has sensible defaults and can typically be omitted. Note: Only supported by the claude engine." + }, + "concurrency": { + "oneOf": [ + { + "type": "string", + "description": "Simple concurrency group name. Gets converted to GitHub Actions concurrency format with the specified group." + }, + { + "type": "object", + "description": "GitHub Actions concurrency configuration for the agent job. Controls how many agentic workflow runs can run concurrently.", + "properties": { + "group": { + "type": "string", + "description": "Concurrency group identifier. Use GitHub Actions expressions like ${{ github.workflow }} or ${{ github.ref }}. Defaults to 'gh-aw-{engine-id}' if not specified." + }, + "cancel-in-progress": { + "type": "boolean", + "description": "Whether to cancel in-progress runs of the same concurrency group. Defaults to false for agentic workflow runs." + } + }, + "required": ["group"], + "additionalProperties": false + } + ], + "description": "Agent job concurrency configuration. Defaults to single job per engine across all workflows (group: 'gh-aw-{engine-id}'). Supports full GitHub Actions concurrency syntax." + }, + "user-agent": { + "type": "string", + "description": "Custom user agent string for GitHub MCP server configuration (codex engine only)" + }, + "env": { + "type": "object", + "description": "Custom environment variables to pass to the AI engine, including secret overrides (e.g., OPENAI_API_KEY: ${{ secrets.CUSTOM_KEY }})", + "additionalProperties": { + "type": "string" + } + }, + "steps": { + "type": "array", + "description": "Custom GitHub Actions steps for 'custom' engine. Define your own deterministic workflow steps instead of using AI processing.", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "error_patterns": { + "type": "array", + "description": "Custom error patterns for validating agent logs", + "items": { + "type": "object", + "description": "Error pattern definition", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this error pattern" + }, + "pattern": { + "type": "string", + "description": "Ecma script regular expression pattern to match log lines" + }, + "level_group": { + "type": "integer", + "minimum": 0, + "description": "Capture group index (1-based) that contains the error level. Use 0 to infer from pattern content." + }, + "message_group": { + "type": "integer", + "minimum": 0, + "description": "Capture group index (1-based) that contains the error message. Use 0 to use the entire match." + }, + "description": { + "type": "string", + "description": "Human-readable description of what this pattern matches" + } + }, + "required": ["pattern"], + "additionalProperties": false + } + }, + "config": { + "type": "string", + "description": "Additional TOML configuration text that will be appended to the generated config.toml in the action (codex engine only)" + }, + "args": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Optional array of command-line arguments to pass to the AI engine CLI. These arguments are injected after all other args but before the prompt." + } + }, + "required": ["id"], + "additionalProperties": false + } + ] + }, + "stdio_mcp_tool": { + "type": "object", + "description": "Stdio MCP tool configuration", + "properties": { + "type": { + "type": "string", + "enum": ["stdio", "local"], + "description": "MCP connection type for stdio (local is an alias for stdio)" + }, + "registry": { + "type": "string", + "description": "URI to the installation location when MCP is installed from a registry" + }, + "command": { + "type": "string", + "minLength": 1, + "description": "Command for stdio MCP connections" + }, + "container": { + "type": "string", + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9/:_.-]*$", + "description": "Container image for stdio MCP connections (alternative to command)" + }, + "version": { + "type": ["string", "number"], + "description": "Optional version/tag for the container image (e.g., 'latest', 'v1.0.0', 20, 3.11). Numeric values are automatically converted to strings at runtime.", + "examples": ["latest", "v1.0.0", 20, 3.11] + }, + "args": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Arguments for command or container execution" + }, + "entrypointArgs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Arguments to add after the container image (container entrypoint arguments)" + }, + "env": { + "type": "object", + "patternProperties": { + "^[A-Z_][A-Z0-9_]*$": { + "type": "string" + } + }, + "additionalProperties": false, + "description": "Environment variables for MCP server" + }, + "network": { + "type": "object", + "properties": { + "allowed": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?)*$", + "description": "Allowed domain name" + }, + "minItems": 1, + "uniqueItems": true, + "description": "List of allowed domain names for network access" + }, + "proxy-args": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Custom proxy arguments for container-based MCP servers" + } + }, + "additionalProperties": false, + "description": "Network configuration for container-based MCP servers" + }, + "allowed": { + "type": "array", + "description": "List of allowed tool functions", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "anyOf": [ + { + "required": ["type"] + }, + { + "required": ["command"] + }, + { + "required": ["container"] + } + ], + "not": { + "allOf": [ + { + "required": ["command"] + }, + { + "required": ["container"] + } + ] + }, + "allOf": [ + { + "if": { + "required": ["network"] + }, + "then": { + "required": ["container"] + } + }, + { + "if": { + "properties": { + "type": { + "enum": ["stdio", "local"] + } + } + }, + "then": { + "anyOf": [ + { + "required": ["command"] + }, + { + "required": ["container"] + } + ] + } + } + ] + }, + "http_mcp_tool": { + "type": "object", + "description": "HTTP MCP tool configuration", + "properties": { + "type": { + "type": "string", + "const": "http", + "description": "MCP connection type for HTTP" + }, + "registry": { + "type": "string", + "description": "URI to the installation location when MCP is installed from a registry" + }, + "url": { + "type": "string", + "minLength": 1, + "description": "URL for HTTP MCP connections" + }, + "headers": { + "type": "object", + "patternProperties": { + "^[A-Za-z0-9_-]+$": { + "type": "string" + } + }, + "additionalProperties": false, + "description": "HTTP headers for HTTP MCP connections" + }, + "allowed": { + "type": "array", + "description": "List of allowed tool functions", + "items": { + "type": "string" + } + } + }, + "required": ["url"], + "additionalProperties": false + }, + "github_token": { + "type": "string", + "pattern": "^\\$\\{\\{\\s*secrets\\.[A-Za-z_][A-Za-z0-9_]*(\\s*\\|\\|\\s*secrets\\.[A-Za-z_][A-Za-z0-9_]*)*\\s*\\}\\}$", + "description": "GitHub token expression using secrets. Pattern details: `[A-Za-z_][A-Za-z0-9_]*` matches a valid secret name (starts with a letter or underscore, followed by letters, digits, or underscores). The full pattern matches expressions like `${{ secrets.NAME }}` or `${{ secrets.NAME1 || secrets.NAME2 }}`.", + "examples": ["${{ secrets.GITHUB_TOKEN }}", "${{ secrets.CUSTOM_PAT }}", "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"] + } + } +} diff --git a/.github/workflows/ai-moderator.lock.yml b/.github/workflows/ai-moderator.lock.yml index 105a9537451..4388c83ef65 100644 --- a/.github/workflows/ai-moderator.lock.yml +++ b/.github/workflows/ai-moderator.lock.yml @@ -2718,10 +2718,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2740,131 +2737,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2881,119 +2888,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3006,29 +2913,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index b3ff43d2c7e..99a288e38b3 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -202,10 +202,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -224,131 +221,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeIncomingText(content, maxLength) { return sanitizeContentCore(content, maxLength); @@ -3486,10 +3493,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3508,131 +3512,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3649,119 +3663,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3774,29 +3688,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml index ad2aeee1b4a..8db892e5a0f 100644 --- a/.github/workflows/artifacts-summary.lock.yml +++ b/.github/workflows/artifacts-summary.lock.yml @@ -2662,10 +2662,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2684,131 +2681,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2825,119 +2832,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2950,29 +2857,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 91249201aa0..6f4d2909264 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -3466,10 +3466,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3488,131 +3485,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3629,119 +3636,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3754,29 +3661,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/blog-auditor.lock.yml b/.github/workflows/blog-auditor.lock.yml index f53e514025c..c5a3e3783fe 100644 --- a/.github/workflows/blog-auditor.lock.yml +++ b/.github/workflows/blog-auditor.lock.yml @@ -2963,10 +2963,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2985,131 +2982,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3126,119 +3133,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3251,29 +3158,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index 5f2d2480587..6bed4149a31 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -194,10 +194,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -216,131 +213,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeIncomingText(content, maxLength) { return sanitizeContentCore(content, maxLength); @@ -3371,10 +3378,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3393,131 +3397,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3534,119 +3548,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3659,29 +3573,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/breaking-change-checker.lock.yml b/.github/workflows/breaking-change-checker.lock.yml index e4ddbd4d238..7542dddb302 100644 --- a/.github/workflows/breaking-change-checker.lock.yml +++ b/.github/workflows/breaking-change-checker.lock.yml @@ -2717,10 +2717,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2739,131 +2736,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2880,119 +2887,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3005,29 +2912,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/campaign-generator.lock.yml b/.github/workflows/campaign-generator.lock.yml index a6f7973e136..75ae078a0bf 100644 --- a/.github/workflows/campaign-generator.lock.yml +++ b/.github/workflows/campaign-generator.lock.yml @@ -2645,10 +2645,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2667,131 +2664,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2808,119 +2815,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2933,29 +2840,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index 68ee60c95f6..efa1187228b 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -202,10 +202,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -224,131 +221,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeIncomingText(content, maxLength) { return sanitizeContentCore(content, maxLength); @@ -3510,10 +3517,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3532,131 +3536,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3673,119 +3687,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3798,29 +3712,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/ci-coach.lock.yml b/.github/workflows/ci-coach.lock.yml index 41e87c72fb4..4756fdb8426 100644 --- a/.github/workflows/ci-coach.lock.yml +++ b/.github/workflows/ci-coach.lock.yml @@ -3467,10 +3467,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3489,131 +3486,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3630,119 +3637,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3755,29 +3662,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 9da592ad858..82028c6f4ac 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -2797,10 +2797,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2819,131 +2816,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2960,119 +2967,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3085,29 +2992,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/cli-consistency-checker.lock.yml b/.github/workflows/cli-consistency-checker.lock.yml index f16875458c7..85d71206421 100644 --- a/.github/workflows/cli-consistency-checker.lock.yml +++ b/.github/workflows/cli-consistency-checker.lock.yml @@ -2713,10 +2713,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2735,131 +2732,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2876,119 +2883,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3001,29 +2908,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index 3f8dd64a403..1ecd0d14048 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -2965,10 +2965,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2987,131 +2984,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3128,119 +3135,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3253,29 +3160,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 335322d1ce8..ce3211ec2e0 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -228,10 +228,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -250,131 +247,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeIncomingText(content, maxLength) { return sanitizeContentCore(content, maxLength); @@ -3833,10 +3840,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3855,131 +3859,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3996,119 +4010,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -4121,29 +4035,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/close-old-discussions.lock.yml b/.github/workflows/close-old-discussions.lock.yml index 97bf523f800..6edc53a5b33 100644 --- a/.github/workflows/close-old-discussions.lock.yml +++ b/.github/workflows/close-old-discussions.lock.yml @@ -2692,10 +2692,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2714,131 +2711,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2855,119 +2862,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2980,29 +2887,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/commit-changes-analyzer.lock.yml b/.github/workflows/commit-changes-analyzer.lock.yml index 8f7a5e353e4..b7c7872ce6a 100644 --- a/.github/workflows/commit-changes-analyzer.lock.yml +++ b/.github/workflows/commit-changes-analyzer.lock.yml @@ -2884,10 +2884,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2906,131 +2903,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3047,119 +3054,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3172,29 +3079,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index a73cd157ce3..0dec0c230c8 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -3275,10 +3275,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3297,131 +3294,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3438,119 +3445,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3563,29 +3470,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/copilot-pr-merged-report.lock.yml b/.github/workflows/copilot-pr-merged-report.lock.yml index 60461f40632..4d35ae25274 100644 --- a/.github/workflows/copilot-pr-merged-report.lock.yml +++ b/.github/workflows/copilot-pr-merged-report.lock.yml @@ -4091,10 +4091,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -4113,131 +4110,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -4254,119 +4261,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -4379,29 +4286,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index e0b38d4db2d..690934e2e73 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -3501,10 +3501,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3523,131 +3520,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3664,119 +3671,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3789,29 +3696,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 3044a0cbdcc..54bd59489bb 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -2993,10 +2993,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3015,131 +3012,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3156,119 +3163,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3281,29 +3188,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index b377d2952e8..d15a9ab3c70 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -4007,10 +4007,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -4029,131 +4026,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -4170,119 +4177,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -4295,29 +4202,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index d0730d60320..b8b2ab206ca 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -190,10 +190,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -212,131 +209,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeIncomingText(content, maxLength) { return sanitizeContentCore(content, maxLength); @@ -3552,10 +3559,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3574,131 +3578,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3715,119 +3729,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3840,29 +3754,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-assign-issue-to-user.lock.yml b/.github/workflows/daily-assign-issue-to-user.lock.yml index a01d6093d41..53f7647403b 100644 --- a/.github/workflows/daily-assign-issue-to-user.lock.yml +++ b/.github/workflows/daily-assign-issue-to-user.lock.yml @@ -2506,10 +2506,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2528,131 +2525,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2669,119 +2676,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2794,29 +2701,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-code-metrics.lock.yml b/.github/workflows/daily-code-metrics.lock.yml index dda2fc510ec..0074b05de5a 100644 --- a/.github/workflows/daily-code-metrics.lock.yml +++ b/.github/workflows/daily-code-metrics.lock.yml @@ -3509,10 +3509,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3531,131 +3528,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3672,119 +3679,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3797,29 +3704,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-copilot-token-report.lock.yml b/.github/workflows/daily-copilot-token-report.lock.yml index bd6589a2f0a..97d6c3fb094 100644 --- a/.github/workflows/daily-copilot-token-report.lock.yml +++ b/.github/workflows/daily-copilot-token-report.lock.yml @@ -3587,10 +3587,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3609,131 +3606,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3750,119 +3757,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3875,29 +3782,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index 8a6c2b9127e..6c450b99447 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -2802,10 +2802,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2824,131 +2821,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2965,119 +2972,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3090,29 +2997,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml index 96d6a7a78e8..01919b88a6e 100644 --- a/.github/workflows/daily-fact.lock.yml +++ b/.github/workflows/daily-fact.lock.yml @@ -2514,10 +2514,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2536,131 +2533,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2677,119 +2684,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2802,29 +2709,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-file-diet.lock.yml b/.github/workflows/daily-file-diet.lock.yml index 9cfbba376d7..fa84c2bd1e7 100644 --- a/.github/workflows/daily-file-diet.lock.yml +++ b/.github/workflows/daily-file-diet.lock.yml @@ -3539,10 +3539,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3561,131 +3558,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3702,119 +3709,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3827,29 +3734,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml index a2b7c53cd63..9a0f0ea5dac 100644 --- a/.github/workflows/daily-firewall-report.lock.yml +++ b/.github/workflows/daily-firewall-report.lock.yml @@ -3267,10 +3267,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3289,131 +3286,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3430,119 +3437,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3555,29 +3462,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-issues-report.lock.yml b/.github/workflows/daily-issues-report.lock.yml index 523bb45aba7..efe15957781 100644 --- a/.github/workflows/daily-issues-report.lock.yml +++ b/.github/workflows/daily-issues-report.lock.yml @@ -3679,10 +3679,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3701,131 +3698,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3842,119 +3849,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3967,29 +3874,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-malicious-code-scan.lock.yml b/.github/workflows/daily-malicious-code-scan.lock.yml index b5b22b76d89..fde57408cfa 100644 --- a/.github/workflows/daily-malicious-code-scan.lock.yml +++ b/.github/workflows/daily-malicious-code-scan.lock.yml @@ -2837,10 +2837,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2859,131 +2856,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3000,119 +3007,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3125,29 +3032,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index 3b312a0e4ab..6e4030d44bc 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -2784,10 +2784,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2806,131 +2803,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2947,119 +2954,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3072,29 +2979,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index d80ca55b011..bb799ad9ccc 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -3392,10 +3392,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3414,131 +3411,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3555,119 +3562,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3680,29 +3587,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-performance-summary.lock.yml b/.github/workflows/daily-performance-summary.lock.yml index 847cc6a47f4..5c27fb464d8 100644 --- a/.github/workflows/daily-performance-summary.lock.yml +++ b/.github/workflows/daily-performance-summary.lock.yml @@ -5135,10 +5135,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -5157,131 +5154,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -5298,119 +5305,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -5423,29 +5330,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-repo-chronicle.lock.yml b/.github/workflows/daily-repo-chronicle.lock.yml index d4e0637d77a..c2cc0a25fc4 100644 --- a/.github/workflows/daily-repo-chronicle.lock.yml +++ b/.github/workflows/daily-repo-chronicle.lock.yml @@ -3313,10 +3313,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3335,131 +3332,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3476,119 +3483,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3601,29 +3508,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-team-status.lock.yml b/.github/workflows/daily-team-status.lock.yml index c93c6c75962..b487e8e8a69 100644 --- a/.github/workflows/daily-team-status.lock.yml +++ b/.github/workflows/daily-team-status.lock.yml @@ -2569,10 +2569,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2591,131 +2588,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2732,119 +2739,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2857,29 +2764,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/daily-workflow-updater.lock.yml b/.github/workflows/daily-workflow-updater.lock.yml index a12aebf05c7..83af3b956bd 100644 --- a/.github/workflows/daily-workflow-updater.lock.yml +++ b/.github/workflows/daily-workflow-updater.lock.yml @@ -2665,10 +2665,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2687,131 +2684,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2828,119 +2835,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2953,29 +2860,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index 442b404be8e..c180aa6a34f 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -3159,10 +3159,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3181,131 +3178,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3322,119 +3329,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3447,29 +3354,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/dependabot-go-checker.lock.yml b/.github/workflows/dependabot-go-checker.lock.yml index c23e0a1e87f..63a78fa1e01 100644 --- a/.github/workflows/dependabot-go-checker.lock.yml +++ b/.github/workflows/dependabot-go-checker.lock.yml @@ -2982,10 +2982,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3004,131 +3001,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3145,119 +3152,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3270,29 +3177,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index 74ee302372b..10db14903d6 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -2654,10 +2654,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2676,131 +2673,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2817,119 +2824,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2942,29 +2849,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index 802fc8d3af4..604cd4e68ec 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -3861,10 +3861,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3883,131 +3880,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -4024,119 +4031,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -4149,29 +4056,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index 526d1a7b546..4aba5dae17c 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -3421,10 +3421,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3443,131 +3440,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3584,119 +3591,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3709,29 +3616,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index f7920c02dc9..d9c5d0198cc 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -2630,10 +2630,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2652,131 +2649,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2793,119 +2800,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2918,29 +2825,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/docs-noob-tester.lock.yml b/.github/workflows/docs-noob-tester.lock.yml index f072c46bcb6..49a74717591 100644 --- a/.github/workflows/docs-noob-tester.lock.yml +++ b/.github/workflows/docs-noob-tester.lock.yml @@ -2725,10 +2725,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2747,131 +2744,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2888,119 +2895,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3013,29 +2920,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index 117eadc306c..2ff763a388e 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -2747,10 +2747,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2769,131 +2766,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2910,119 +2917,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3035,29 +2942,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/example-workflow-analyzer.lock.yml b/.github/workflows/example-workflow-analyzer.lock.yml index 4333dc63abc..76f4c6d9850 100644 --- a/.github/workflows/example-workflow-analyzer.lock.yml +++ b/.github/workflows/example-workflow-analyzer.lock.yml @@ -2633,10 +2633,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2655,131 +2652,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2796,119 +2803,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2921,29 +2828,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/github-mcp-structural-analysis.lock.yml b/.github/workflows/github-mcp-structural-analysis.lock.yml index 0b4af183045..75a78c1a6f5 100644 --- a/.github/workflows/github-mcp-structural-analysis.lock.yml +++ b/.github/workflows/github-mcp-structural-analysis.lock.yml @@ -3360,10 +3360,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3382,131 +3379,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3523,119 +3530,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3648,29 +3555,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index b81086cee81..b03809e2162 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -3232,10 +3232,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3254,131 +3251,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3395,119 +3402,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3520,29 +3427,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index 057c577309f..21c4a6cc8bc 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -3250,10 +3250,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3272,131 +3269,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3413,119 +3420,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3538,29 +3445,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/go-fan.lock.yml b/.github/workflows/go-fan.lock.yml index d9d32b52cba..a109aab27cb 100644 --- a/.github/workflows/go-fan.lock.yml +++ b/.github/workflows/go-fan.lock.yml @@ -3019,10 +3019,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3041,131 +3038,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3182,119 +3189,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3307,29 +3214,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/go-file-size-reduction-project64.campaign.g.lock.yml b/.github/workflows/go-file-size-reduction-project64.campaign.g.lock.yml index 00d80937864..d89e2fd8124 100644 --- a/.github/workflows/go-file-size-reduction-project64.campaign.g.lock.yml +++ b/.github/workflows/go-file-size-reduction-project64.campaign.g.lock.yml @@ -2702,10 +2702,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2724,131 +2721,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2865,119 +2872,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2990,29 +2897,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/go-file-size-reduction.campaign.g.lock.yml b/.github/workflows/go-file-size-reduction.campaign.g.lock.yml index 043beb5c554..b1158e8d2bf 100644 --- a/.github/workflows/go-file-size-reduction.campaign.g.lock.yml +++ b/.github/workflows/go-file-size-reduction.campaign.g.lock.yml @@ -2702,10 +2702,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2724,131 +2721,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2865,119 +2872,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2990,29 +2897,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index fc09eb4105f..32fc9df0a81 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -2880,10 +2880,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2902,131 +2899,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3043,119 +3050,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3168,29 +3075,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/go-pattern-detector.lock.yml b/.github/workflows/go-pattern-detector.lock.yml index aacf5d9f669..dea4c0c3081 100644 --- a/.github/workflows/go-pattern-detector.lock.yml +++ b/.github/workflows/go-pattern-detector.lock.yml @@ -2726,10 +2726,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2748,131 +2745,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2889,119 +2896,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3014,29 +2921,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml index e169ebc4560..20c848095fe 100644 --- a/.github/workflows/grumpy-reviewer.lock.yml +++ b/.github/workflows/grumpy-reviewer.lock.yml @@ -195,10 +195,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -217,131 +214,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeIncomingText(content, maxLength) { return sanitizeContentCore(content, maxLength); @@ -3482,10 +3489,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3504,131 +3508,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3645,119 +3659,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3770,29 +3684,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/hourly-ci-cleaner.lock.yml b/.github/workflows/hourly-ci-cleaner.lock.yml index 5589b44edc6..c014f77599a 100644 --- a/.github/workflows/hourly-ci-cleaner.lock.yml +++ b/.github/workflows/hourly-ci-cleaner.lock.yml @@ -2891,10 +2891,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2913,131 +2910,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3054,119 +3061,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3179,29 +3086,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/human-ai-collaboration.lock.yml b/.github/workflows/human-ai-collaboration.lock.yml index 40cbc568a94..d369afc935f 100644 --- a/.github/workflows/human-ai-collaboration.lock.yml +++ b/.github/workflows/human-ai-collaboration.lock.yml @@ -3025,10 +3025,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3047,131 +3044,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3188,119 +3195,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3313,29 +3220,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/incident-response.lock.yml b/.github/workflows/incident-response.lock.yml index b88a3a9263b..e87da72a550 100644 --- a/.github/workflows/incident-response.lock.yml +++ b/.github/workflows/incident-response.lock.yml @@ -3174,10 +3174,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3196,131 +3193,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3337,119 +3344,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3462,29 +3369,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index 8e3f3cf8cd6..aba4a8a839a 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -2760,10 +2760,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2782,131 +2779,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2923,119 +2930,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3048,29 +2955,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/intelligence.lock.yml b/.github/workflows/intelligence.lock.yml index 8b6d3f93c32..628cd0724c3 100644 --- a/.github/workflows/intelligence.lock.yml +++ b/.github/workflows/intelligence.lock.yml @@ -3705,10 +3705,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3727,131 +3724,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3868,119 +3875,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3993,29 +3900,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/issue-arborist.lock.yml b/.github/workflows/issue-arborist.lock.yml index f124d0f6771..f7ce2e4713e 100644 --- a/.github/workflows/issue-arborist.lock.yml +++ b/.github/workflows/issue-arborist.lock.yml @@ -2817,10 +2817,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2839,131 +2836,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2980,119 +2987,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3105,29 +3012,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index d09ebbdb029..4b01e805831 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -191,10 +191,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -213,131 +210,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeIncomingText(content, maxLength) { return sanitizeContentCore(content, maxLength); @@ -3192,10 +3199,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3214,131 +3218,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3355,119 +3369,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3480,29 +3394,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index 51d6a7cfe09..621aae9ad09 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -2718,10 +2718,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2740,131 +2737,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2881,119 +2888,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3006,29 +2913,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/issue-triage-agent.lock.yml b/.github/workflows/issue-triage-agent.lock.yml index f6f2fc2dc72..0f56ea0343a 100644 --- a/.github/workflows/issue-triage-agent.lock.yml +++ b/.github/workflows/issue-triage-agent.lock.yml @@ -2481,10 +2481,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2503,131 +2500,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2644,119 +2651,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2769,29 +2676,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/jsweep.lock.yml b/.github/workflows/jsweep.lock.yml index 7035b1cc278..f324b738d55 100644 --- a/.github/workflows/jsweep.lock.yml +++ b/.github/workflows/jsweep.lock.yml @@ -2756,10 +2756,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2778,131 +2775,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2919,119 +2926,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3044,29 +2951,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/layout-spec-maintainer.lock.yml b/.github/workflows/layout-spec-maintainer.lock.yml index 83bfc57d1f0..761a085bca2 100644 --- a/.github/workflows/layout-spec-maintainer.lock.yml +++ b/.github/workflows/layout-spec-maintainer.lock.yml @@ -2783,10 +2783,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2805,131 +2802,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2946,119 +2953,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3071,29 +2978,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index 63e36ed98ef..44717a7ed04 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -3013,10 +3013,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3035,131 +3032,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3176,119 +3183,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3301,29 +3208,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index 0dbbba5c344..c33812a83c1 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -3149,10 +3149,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3171,131 +3168,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3312,119 +3319,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3437,29 +3344,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index 8c953c8c584..92edc0755fb 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -3273,10 +3273,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3295,131 +3292,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3436,119 +3443,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3561,29 +3468,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/notion-issue-summary.lock.yml b/.github/workflows/notion-issue-summary.lock.yml index 89064137988..b84eade5b21 100644 --- a/.github/workflows/notion-issue-summary.lock.yml +++ b/.github/workflows/notion-issue-summary.lock.yml @@ -2488,10 +2488,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2510,131 +2507,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2651,119 +2658,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2776,29 +2683,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/org-health-report.lock.yml b/.github/workflows/org-health-report.lock.yml index 9797581e560..aae4a90d7f4 100644 --- a/.github/workflows/org-health-report.lock.yml +++ b/.github/workflows/org-health-report.lock.yml @@ -3427,10 +3427,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3449,131 +3446,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3590,119 +3597,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3715,29 +3622,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/org-wide-rollout.lock.yml b/.github/workflows/org-wide-rollout.lock.yml index 6db63433232..9b52c9f6777 100644 --- a/.github/workflows/org-wide-rollout.lock.yml +++ b/.github/workflows/org-wide-rollout.lock.yml @@ -3202,10 +3202,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3224,131 +3221,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3365,119 +3372,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3490,29 +3397,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 9e6eba3f7e7..4773aca54f3 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -212,10 +212,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -234,131 +231,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeIncomingText(content, maxLength) { return sanitizeContentCore(content, maxLength); @@ -3472,10 +3479,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3494,131 +3498,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3635,119 +3649,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3760,29 +3674,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index 09ba94b2f82..7a238dce4ff 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -194,10 +194,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -216,131 +213,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeIncomingText(content, maxLength) { return sanitizeContentCore(content, maxLength); @@ -3514,10 +3521,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3536,131 +3540,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3677,119 +3691,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3802,29 +3716,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 3e9e2a4405d..afd54319951 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -197,10 +197,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -219,131 +216,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeIncomingText(content, maxLength) { return sanitizeContentCore(content, maxLength); @@ -3905,10 +3912,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3927,131 +3931,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -4068,119 +4082,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -4193,29 +4107,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/portfolio-analyst.lock.yml b/.github/workflows/portfolio-analyst.lock.yml index ca521539e8f..3e48348fb2e 100644 --- a/.github/workflows/portfolio-analyst.lock.yml +++ b/.github/workflows/portfolio-analyst.lock.yml @@ -3619,10 +3619,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3641,131 +3638,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3782,119 +3789,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3907,29 +3814,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/pr-nitpick-reviewer.lock.yml b/.github/workflows/pr-nitpick-reviewer.lock.yml index f7501042b5f..ff1b16226e8 100644 --- a/.github/workflows/pr-nitpick-reviewer.lock.yml +++ b/.github/workflows/pr-nitpick-reviewer.lock.yml @@ -3547,10 +3547,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3569,131 +3566,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3710,119 +3717,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3835,29 +3742,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index ff604f2d9df..645a745aed1 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -3648,10 +3648,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3670,131 +3667,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3811,119 +3818,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3936,29 +3843,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml index 048fa1aa369..2a1fbda6bbe 100644 --- a/.github/workflows/python-data-charts.lock.yml +++ b/.github/workflows/python-data-charts.lock.yml @@ -3653,10 +3653,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3675,131 +3672,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3816,119 +3823,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3941,29 +3848,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index b14f091dcb1..c113c6437d8 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -223,10 +223,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -245,131 +242,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeIncomingText(content, maxLength) { return sanitizeContentCore(content, maxLength); @@ -3828,10 +3835,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3850,131 +3854,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3991,119 +4005,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -4116,29 +4030,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/release.lock.yml b/.github/workflows/release.lock.yml index a9963501579..2fc7cf1cc45 100644 --- a/.github/workflows/release.lock.yml +++ b/.github/workflows/release.lock.yml @@ -2652,10 +2652,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2674,131 +2671,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2815,119 +2822,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2940,29 +2847,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/repo-tree-map.lock.yml b/.github/workflows/repo-tree-map.lock.yml index 1a9caaa1470..1e194211869 100644 --- a/.github/workflows/repo-tree-map.lock.yml +++ b/.github/workflows/repo-tree-map.lock.yml @@ -2664,10 +2664,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2686,131 +2683,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2827,119 +2834,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2952,29 +2859,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/repository-quality-improver.lock.yml b/.github/workflows/repository-quality-improver.lock.yml index f5d17bdc22f..a15ab7eb954 100644 --- a/.github/workflows/repository-quality-improver.lock.yml +++ b/.github/workflows/repository-quality-improver.lock.yml @@ -3215,10 +3215,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3237,131 +3234,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3378,119 +3385,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3503,29 +3410,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/research.lock.yml b/.github/workflows/research.lock.yml index e769f38dfc1..8c62045fb49 100644 --- a/.github/workflows/research.lock.yml +++ b/.github/workflows/research.lock.yml @@ -2632,10 +2632,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2654,131 +2651,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2795,119 +2802,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2920,29 +2827,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml index 33d96e5aba5..9ed57f62d45 100644 --- a/.github/workflows/safe-output-health.lock.yml +++ b/.github/workflows/safe-output-health.lock.yml @@ -3182,10 +3182,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3204,131 +3201,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3345,119 +3352,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3470,29 +3377,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml index 4168df403d2..3488b75160d 100644 --- a/.github/workflows/schema-consistency-checker.lock.yml +++ b/.github/workflows/schema-consistency-checker.lock.yml @@ -2949,10 +2949,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2971,131 +2968,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3112,119 +3119,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3237,29 +3144,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index b203dd013ce..80cdd5fe182 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -238,10 +238,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -260,131 +257,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeIncomingText(content, maxLength) { return sanitizeContentCore(content, maxLength); @@ -3808,10 +3815,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3830,131 +3834,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3971,119 +3985,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -4096,29 +4010,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/security-compliance.lock.yml b/.github/workflows/security-compliance.lock.yml index 5c806033d19..b4b683f05f9 100644 --- a/.github/workflows/security-compliance.lock.yml +++ b/.github/workflows/security-compliance.lock.yml @@ -2845,10 +2845,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2867,131 +2864,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3008,119 +3015,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3133,29 +3040,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml index 78fb1008de8..cee0ee14b72 100644 --- a/.github/workflows/security-fix-pr.lock.yml +++ b/.github/workflows/security-fix-pr.lock.yml @@ -2770,10 +2770,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2792,131 +2789,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2933,119 +2940,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3058,29 +2965,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index df2f1df9061..95357d6502f 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -3203,10 +3203,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3225,131 +3222,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3366,119 +3373,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3491,29 +3398,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/slide-deck-maintainer.lock.yml b/.github/workflows/slide-deck-maintainer.lock.yml index b1f9f97635e..dd86243fe4c 100644 --- a/.github/workflows/slide-deck-maintainer.lock.yml +++ b/.github/workflows/slide-deck-maintainer.lock.yml @@ -2850,10 +2850,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2872,131 +2869,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3013,119 +3020,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3138,29 +3045,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index ba2ff1e4280..d9a071f28dc 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -3330,10 +3330,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3352,131 +3349,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3493,119 +3500,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3618,29 +3525,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/smoke-codex-firewall.lock.yml b/.github/workflows/smoke-codex-firewall.lock.yml index 3a1283bb28f..e4901e6ce35 100644 --- a/.github/workflows/smoke-codex-firewall.lock.yml +++ b/.github/workflows/smoke-codex-firewall.lock.yml @@ -3032,10 +3032,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3054,131 +3051,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3195,119 +3202,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3320,29 +3227,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 2472d91991a..4e7505819c1 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -3138,10 +3138,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3160,131 +3157,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3301,119 +3308,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3426,29 +3333,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/smoke-copilot-no-firewall.lock.yml b/.github/workflows/smoke-copilot-no-firewall.lock.yml index ec7de7bbfd8..9fa16b01d5e 100644 --- a/.github/workflows/smoke-copilot-no-firewall.lock.yml +++ b/.github/workflows/smoke-copilot-no-firewall.lock.yml @@ -4426,10 +4426,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -4448,131 +4445,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -4589,119 +4596,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -4714,29 +4621,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/smoke-copilot-playwright.lock.yml b/.github/workflows/smoke-copilot-playwright.lock.yml index 2ed1950a1f0..08c48d77781 100644 --- a/.github/workflows/smoke-copilot-playwright.lock.yml +++ b/.github/workflows/smoke-copilot-playwright.lock.yml @@ -4540,10 +4540,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -4562,131 +4559,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -4703,119 +4710,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -4828,29 +4735,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/smoke-copilot-safe-inputs.lock.yml b/.github/workflows/smoke-copilot-safe-inputs.lock.yml index d07d2f48065..f44bc5f5add 100644 --- a/.github/workflows/smoke-copilot-safe-inputs.lock.yml +++ b/.github/workflows/smoke-copilot-safe-inputs.lock.yml @@ -4242,10 +4242,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -4264,131 +4261,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -4405,119 +4412,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -4530,29 +4437,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 649d00f357b..947e8139418 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -3082,10 +3082,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3104,131 +3101,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3245,119 +3252,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3370,29 +3277,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/smoke-detector.lock.yml b/.github/workflows/smoke-detector.lock.yml index 4aca069c30e..b7611e70b75 100644 --- a/.github/workflows/smoke-detector.lock.yml +++ b/.github/workflows/smoke-detector.lock.yml @@ -3445,10 +3445,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3467,131 +3464,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3608,119 +3615,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3733,29 +3640,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/smoke-srt.lock.yml b/.github/workflows/smoke-srt.lock.yml index be59a2f71b7..529697c6020 100644 --- a/.github/workflows/smoke-srt.lock.yml +++ b/.github/workflows/smoke-srt.lock.yml @@ -2639,10 +2639,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2661,131 +2658,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2802,119 +2809,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2927,29 +2834,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/spec-kit-execute.lock.yml b/.github/workflows/spec-kit-execute.lock.yml index b585b2d85bb..5a57d2c6b9e 100644 --- a/.github/workflows/spec-kit-execute.lock.yml +++ b/.github/workflows/spec-kit-execute.lock.yml @@ -2949,10 +2949,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2971,131 +2968,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3112,119 +3119,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3237,29 +3144,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/spec-kit-executor.lock.yml b/.github/workflows/spec-kit-executor.lock.yml index 898cf41f71d..38a2f60fae6 100644 --- a/.github/workflows/spec-kit-executor.lock.yml +++ b/.github/workflows/spec-kit-executor.lock.yml @@ -2824,10 +2824,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2846,131 +2843,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2987,119 +2994,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3112,29 +3019,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/speckit-dispatcher.lock.yml b/.github/workflows/speckit-dispatcher.lock.yml index 9cea09b5fee..76341351f9a 100644 --- a/.github/workflows/speckit-dispatcher.lock.yml +++ b/.github/workflows/speckit-dispatcher.lock.yml @@ -217,10 +217,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -239,131 +236,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeIncomingText(content, maxLength) { return sanitizeContentCore(content, maxLength); @@ -3753,10 +3760,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3775,131 +3779,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3916,119 +3930,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -4041,29 +3955,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml index cd5e655cf6c..fdb82a3281a 100644 --- a/.github/workflows/stale-repo-identifier.lock.yml +++ b/.github/workflows/stale-repo-identifier.lock.yml @@ -3581,10 +3581,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3603,131 +3600,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3744,119 +3751,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3869,29 +3776,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index df3b5d72a3c..f489591722d 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -3033,10 +3033,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3055,131 +3052,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3196,119 +3203,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3321,29 +3228,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/sub-issue-closer.lock.yml b/.github/workflows/sub-issue-closer.lock.yml index 4fa68624f8f..cb84f95280c 100644 --- a/.github/workflows/sub-issue-closer.lock.yml +++ b/.github/workflows/sub-issue-closer.lock.yml @@ -2652,10 +2652,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2674,131 +2671,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2815,119 +2822,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2940,29 +2847,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/super-linter.lock.yml b/.github/workflows/super-linter.lock.yml index bc55266dc53..0b962c3c44d 100644 --- a/.github/workflows/super-linter.lock.yml +++ b/.github/workflows/super-linter.lock.yml @@ -2785,10 +2785,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2807,131 +2804,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2948,119 +2955,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3073,29 +2980,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index 8578b200652..50524052275 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -3050,10 +3050,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3072,131 +3069,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3213,119 +3220,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3338,29 +3245,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 445ed039ee7..5e273cfc78e 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -3078,10 +3078,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3100,131 +3097,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3241,119 +3248,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3366,29 +3273,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/typist.lock.yml b/.github/workflows/typist.lock.yml index 73bf16d0e93..fdd539e06bf 100644 --- a/.github/workflows/typist.lock.yml +++ b/.github/workflows/typist.lock.yml @@ -3201,10 +3201,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3223,131 +3220,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3364,119 +3371,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3489,29 +3396,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index bb9ca84be55..8b711ea515f 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -3527,10 +3527,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3549,131 +3546,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3690,119 +3697,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3815,29 +3722,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/video-analyzer.lock.yml b/.github/workflows/video-analyzer.lock.yml index a722bf0af00..e798b17227e 100644 --- a/.github/workflows/video-analyzer.lock.yml +++ b/.github/workflows/video-analyzer.lock.yml @@ -2816,10 +2816,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2838,131 +2835,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2979,119 +2986,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3104,29 +3011,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/weekly-issue-summary.lock.yml b/.github/workflows/weekly-issue-summary.lock.yml index 32236e422d9..a670d423b60 100644 --- a/.github/workflows/weekly-issue-summary.lock.yml +++ b/.github/workflows/weekly-issue-summary.lock.yml @@ -3221,10 +3221,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3243,131 +3240,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3384,119 +3391,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3509,29 +3416,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.github/workflows/workflow-generator.lock.yml b/.github/workflows/workflow-generator.lock.yml index d2fa02c979d..106ea183b98 100644 --- a/.github/workflows/workflow-generator.lock.yml +++ b/.github/workflows/workflow-generator.lock.yml @@ -2663,10 +2663,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2685,131 +2682,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } - }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; } - return "(redacted)"; + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); + core.info(`Redacted URL: ${truncated}`); } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(hostname); + return "(redacted)"; + } + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -2826,119 +2833,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -2951,29 +2858,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; diff --git a/.vscode/settings.json b/.vscode/settings.json index c5cd5b35eda..29a40e04cf6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "yaml.hover": true, "yaml.schemas": { "./.github/aw/main_workflow_schema.json": ".github/workflows/*.md", + "./.github/aw/schemas/agentic-workflow.json": ".github/workflows/*.md", "./pkg/parser/schemas/main_workflow_schema.json": ".github/workflows/*.md" }, "yaml.validate": true From c64ff745139b0e706fadac1a6f4316302f8b0c82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 13:26:35 +0000 Subject: [PATCH 3/7] Set GH_AW_WORKFLOW_ID at job level in consolidated safe_outputs job - Added job-level Env map with GH_AW_WORKFLOW_ID in buildConsolidatedSafeOutputsJob - Removed duplicate GH_AW_WORKFLOW_ID from create_pull_request step config (now inherited from job level) - Updated integration tests to check job.Env for GH_AW_WORKFLOW_ID in consolidated jobs - Tests now properly verify job-level env vars instead of looking for them in step content Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../compiler_safe_outputs_consolidated.go | 12 ++++++----- pkg/workflow/safe_outputs_integration_test.go | 20 ++++++++++++++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/pkg/workflow/compiler_safe_outputs_consolidated.go b/pkg/workflow/compiler_safe_outputs_consolidated.go index e55547e61e6..f61bc479e48 100644 --- a/pkg/workflow/compiler_safe_outputs_consolidated.go +++ b/pkg/workflow/compiler_safe_outputs_consolidated.go @@ -504,9 +504,12 @@ func (c *Compiler) buildConsolidatedSafeOutputsJob(data *WorkflowData, mainJobNa RunsOn: c.formatSafeOutputsRunsOn(data.SafeOutputs), Permissions: permissions.RenderToYAML(), TimeoutMinutes: 15, // Slightly longer timeout for consolidated job with multiple steps - Steps: steps, - Outputs: outputs, - Needs: needs, + Env: map[string]string{ + "GH_AW_WORKFLOW_ID": fmt.Sprintf("%q", mainJobName), + }, + Steps: steps, + Outputs: outputs, + Needs: needs, } consolidatedSafeOutputsLog.Printf("Built consolidated safe outputs job with %d steps", len(safeOutputStepNames)) @@ -638,9 +641,8 @@ func (c *Compiler) buildCreatePullRequestStepConfig(data *WorkflowData, mainJobN cfg := data.SafeOutputs.CreatePullRequests var customEnvVars []string - // Pass the workflow ID for branch naming (required by create_pull_request.cjs) - customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_WORKFLOW_ID: %q\n", mainJobName)) // Pass the base branch from GitHub context (required by create_pull_request.cjs) + // Note: GH_AW_WORKFLOW_ID is now set at the job level and inherited by all steps customEnvVars = append(customEnvVars, " GH_AW_BASE_BRANCH: ${{ github.ref_name }}\n") customEnvVars = append(customEnvVars, buildTitlePrefixEnvVar("GH_AW_PR_TITLE_PREFIX", cfg.TitlePrefix)...) customEnvVars = append(customEnvVars, buildLabelsEnvVar("GH_AW_PR_LABELS", cfg.Labels)...) diff --git a/pkg/workflow/safe_outputs_integration_test.go b/pkg/workflow/safe_outputs_integration_test.go index caa8d2b88aa..b5e41d71369 100644 --- a/pkg/workflow/safe_outputs_integration_test.go +++ b/pkg/workflow/safe_outputs_integration_test.go @@ -592,8 +592,19 @@ func TestConsolidatedSafeOutputsJobIntegration(t *testing.T) { // Convert steps to string for verification stepsContent := strings.Join(job.Steps, "") - // Verify expected environment variables and step content + // For consolidated job, GH_AW_WORKFLOW_ID should be at job level, not in steps + // Check job.Env for this variable + if job.Env == nil || job.Env["GH_AW_WORKFLOW_ID"] == "" { + t.Errorf("GH_AW_WORKFLOW_ID should be set at job level in consolidated job for %s.\nJob.Env: %v", + tt.name, job.Env) + } + + // Verify other expected environment variables and step content (excluding GH_AW_WORKFLOW_ID) for _, expectedContent := range tt.expectedStepsContaining { + if expectedContent == "GH_AW_WORKFLOW_ID" { + // Skip GH_AW_WORKFLOW_ID check in steps since it's now at job level + continue + } if !strings.Contains(stepsContent, expectedContent) { t.Errorf("Expected content %q not found in consolidated job for %s.\nJob steps:\n%s", expectedContent, tt.name, stepsContent) @@ -855,10 +866,9 @@ func TestConsolidatedSafeOutputsJobWithCustomEnv(t *testing.T) { } } - // Verify GH_AW_WORKFLOW_ID is present - // Known issue: GH_AW_WORKFLOW_ID may be missing from consolidated job for certain configurations - if !strings.Contains(stepsContent, "GH_AW_WORKFLOW_ID") { - t.Log("Known issue: GH_AW_WORKFLOW_ID not present in consolidated job - this should be fixed") + // Verify GH_AW_WORKFLOW_ID is present at job level + if job.Env == nil || job.Env["GH_AW_WORKFLOW_ID"] == "" { + t.Error("GH_AW_WORKFLOW_ID should be set at job level in consolidated job") } t.Logf("✓ Consolidated job with custom env vars built successfully with %d steps: %v", len(stepNames), stepNames) From 7b524025141bd623ce0504b1398a9c78ccddab71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 13:34:07 +0000 Subject: [PATCH 4/7] Update tests and recompile workflows with job-level GH_AW_WORKFLOW_ID - Updated create_pull_request_env_vars_test.go to not expect GH_AW_WORKFLOW_ID in step-level env vars - Added comments explaining that GH_AW_WORKFLOW_ID is now at job level - Recompiled all workflow lock files with new job-level env configuration - All tests pass (except pre-existing TestCheckFirewallDisable failure) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../agent-performance-analyzer.lock.yml | 366 ++++++------------ .github/workflows/ai-moderator.lock.yml | 2 + .github/workflows/archie.lock.yml | 2 + .github/workflows/artifacts-summary.lock.yml | 2 + .github/workflows/audit-workflows.lock.yml | 2 + .github/workflows/blog-auditor.lock.yml | 2 + .github/workflows/brave.lock.yml | 2 + .../breaking-change-checker.lock.yml | 2 + .github/workflows/campaign-generator.lock.yml | 2 + .github/workflows/campaign-manager.lock.yml | 366 ++++++------------ .github/workflows/changeset.lock.yml | 2 + .github/workflows/ci-coach.lock.yml | 3 +- .github/workflows/ci-doctor.lock.yml | 2 + .../cli-consistency-checker.lock.yml | 2 + .../workflows/cli-version-checker.lock.yml | 2 + .github/workflows/cloclo.lock.yml | 3 +- .../workflows/close-old-discussions.lock.yml | 2 + .../commit-changes-analyzer.lock.yml | 2 + .../workflows/copilot-agent-analysis.lock.yml | 2 + .../copilot-pr-merged-report.lock.yml | 2 + .../copilot-pr-nlp-analysis.lock.yml | 2 + .../copilot-pr-prompt-analysis.lock.yml | 2 + .../copilot-session-insights.lock.yml | 2 + .github/workflows/craft.lock.yml | 2 + .../daily-assign-issue-to-user.lock.yml | 2 + .github/workflows/daily-code-metrics.lock.yml | 2 + .../daily-copilot-token-report.lock.yml | 2 + .github/workflows/daily-doc-updater.lock.yml | 3 +- .github/workflows/daily-fact.lock.yml | 2 + .github/workflows/daily-file-diet.lock.yml | 2 + .../workflows/daily-firewall-report.lock.yml | 2 + .../workflows/daily-issues-report.lock.yml | 2 + .../daily-malicious-code-scan.lock.yml | 2 + .../daily-multi-device-docs-tester.lock.yml | 2 + .github/workflows/daily-news.lock.yml | 2 + .../daily-performance-summary.lock.yml | 2 + .../workflows/daily-repo-chronicle.lock.yml | 2 + .github/workflows/daily-team-status.lock.yml | 2 + .../workflows/daily-workflow-updater.lock.yml | 3 +- .github/workflows/deep-report.lock.yml | 2 + .../workflows/dependabot-go-checker.lock.yml | 2 + .github/workflows/dev-hawk.lock.yml | 2 + .github/workflows/dev.lock.yml | 2 + .../developer-docs-consolidator.lock.yml | 3 +- .github/workflows/dictation-prompt.lock.yml | 3 +- .github/workflows/docs-noob-tester.lock.yml | 2 + .../duplicate-code-detector.lock.yml | 2 + .../example-workflow-analyzer.lock.yml | 2 + .../github-mcp-structural-analysis.lock.yml | 2 + .../github-mcp-tools-report.lock.yml | 3 +- .../workflows/glossary-maintainer.lock.yml | 3 +- .github/workflows/go-fan.lock.yml | 2 + ...ze-reduction-project64.campaign.g.lock.yml | 2 + ...go-file-size-reduction.campaign.g.lock.yml | 2 + .github/workflows/go-logger.lock.yml | 3 +- .../workflows/go-pattern-detector.lock.yml | 2 + .github/workflows/grumpy-reviewer.lock.yml | 2 + .github/workflows/hourly-ci-cleaner.lock.yml | 3 +- .../workflows/human-ai-collaboration.lock.yml | 2 + .github/workflows/incident-response.lock.yml | 3 +- .../workflows/instructions-janitor.lock.yml | 3 +- .github/workflows/intelligence.lock.yml | 2 + .github/workflows/issue-arborist.lock.yml | 2 + .github/workflows/issue-classifier.lock.yml | 2 + .github/workflows/issue-monster.lock.yml | 2 + .github/workflows/issue-triage-agent.lock.yml | 2 + .github/workflows/jsweep.lock.yml | 3 +- .../workflows/layout-spec-maintainer.lock.yml | 3 +- .github/workflows/lockfile-stats.lock.yml | 2 + .github/workflows/mcp-inspector.lock.yml | 2 + .github/workflows/mergefest.lock.yml | 2 + .github/workflows/org-health-report.lock.yml | 2 + .github/workflows/org-wide-rollout.lock.yml | 3 +- .github/workflows/pdf-summary.lock.yml | 2 + .github/workflows/plan.lock.yml | 2 + .github/workflows/poem-bot.lock.yml | 3 +- .github/workflows/portfolio-analyst.lock.yml | 2 + .../workflows/pr-nitpick-reviewer.lock.yml | 2 + .../prompt-clustering-analysis.lock.yml | 2 + .github/workflows/python-data-charts.lock.yml | 2 + .github/workflows/q.lock.yml | 3 +- .github/workflows/release.lock.yml | 2 + .github/workflows/repo-tree-map.lock.yml | 2 + .../repository-quality-improver.lock.yml | 2 + .github/workflows/research.lock.yml | 2 + .github/workflows/safe-output-health.lock.yml | 2 + .../schema-consistency-checker.lock.yml | 2 + .github/workflows/scout.lock.yml | 2 + .../workflows/security-compliance.lock.yml | 2 + .github/workflows/security-fix-pr.lock.yml | 3 +- .../semantic-function-refactor.lock.yml | 2 + .../workflows/slide-deck-maintainer.lock.yml | 3 +- .github/workflows/smoke-claude.lock.yml | 2 + .../workflows/smoke-codex-firewall.lock.yml | 2 + .github/workflows/smoke-codex.lock.yml | 2 + .../smoke-copilot-no-firewall.lock.yml | 2 + .../smoke-copilot-playwright.lock.yml | 2 + .../smoke-copilot-safe-inputs.lock.yml | 2 + .github/workflows/smoke-copilot.lock.yml | 2 + .github/workflows/smoke-detector.lock.yml | 2 + .github/workflows/spec-kit-execute.lock.yml | 3 +- .github/workflows/spec-kit-executor.lock.yml | 3 +- .github/workflows/speckit-dispatcher.lock.yml | 2 + .../workflows/stale-repo-identifier.lock.yml | 2 + .../workflows/static-analysis-report.lock.yml | 2 + .github/workflows/sub-issue-closer.lock.yml | 2 + .github/workflows/super-linter.lock.yml | 2 + .../workflows/technical-doc-writer.lock.yml | 3 +- .github/workflows/tidy.lock.yml | 3 +- .github/workflows/typist.lock.yml | 2 + .github/workflows/unbloat-docs.lock.yml | 3 +- .github/workflows/video-analyzer.lock.yml | 2 + .../workflows/weekly-issue-summary.lock.yml | 2 + .github/workflows/workflow-generator.lock.yml | 2 + .../workflow-health-manager.lock.yml | 366 ++++++------------ .../create_pull_request_env_vars_test.go | 4 +- 116 files changed, 604 insertions(+), 746 deletions(-) diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index 042b498c559..5bacbbcdf46 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -3099,10 +3099,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3121,131 +3118,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3262,119 +3269,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3387,29 +3294,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; @@ -7422,6 +7306,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/ai-moderator.lock.yml b/.github/workflows/ai-moderator.lock.yml index 4388c83ef65..f98a8fff140 100644 --- a/.github/workflows/ai-moderator.lock.yml +++ b/.github/workflows/ai-moderator.lock.yml @@ -6550,6 +6550,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_labels_labels_added: ${{ steps.add_labels.outputs.labels_added }} steps: diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index 99a288e38b3..8c75f35788c 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -7558,6 +7558,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml index 8db892e5a0f..99a7e9d960b 100644 --- a/.github/workflows/artifacts-summary.lock.yml +++ b/.github/workflows/artifacts-summary.lock.yml @@ -6539,6 +6539,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 6f4d2909264..7757f7c2c3a 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -6921,6 +6921,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/blog-auditor.lock.yml b/.github/workflows/blog-auditor.lock.yml index c5a3e3783fe..00838f922f8 100644 --- a/.github/workflows/blog-auditor.lock.yml +++ b/.github/workflows/blog-auditor.lock.yml @@ -6404,6 +6404,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index 6bed4149a31..f3968c3d954 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -7437,6 +7437,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/breaking-change-checker.lock.yml b/.github/workflows/breaking-change-checker.lock.yml index 7542dddb302..a3bc186e5fb 100644 --- a/.github/workflows/breaking-change-checker.lock.yml +++ b/.github/workflows/breaking-change-checker.lock.yml @@ -6773,6 +6773,8 @@ jobs: contents: read issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/campaign-generator.lock.yml b/.github/workflows/campaign-generator.lock.yml index 75ae078a0bf..956d85fa766 100644 --- a/.github/workflows/campaign-generator.lock.yml +++ b/.github/workflows/campaign-generator.lock.yml @@ -6686,6 +6686,8 @@ jobs: contents: read issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: assign_to_agent_assigned: ${{ steps.assign_to_agent.outputs.assigned }} steps: diff --git a/.github/workflows/campaign-manager.lock.yml b/.github/workflows/campaign-manager.lock.yml index 3ebadbb6fbd..c77be5d4370 100644 --- a/.github/workflows/campaign-manager.lock.yml +++ b/.github/workflows/campaign-manager.lock.yml @@ -2971,10 +2971,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -2993,131 +2990,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3134,119 +3141,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3259,29 +3166,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; @@ -7294,6 +7178,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index efa1187228b..36f0c739aa5 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -7451,6 +7451,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: push_to_pull_request_branch_commit_url: ${{ steps.push_to_pull_request_branch.outputs.commit_url }} steps: diff --git a/.github/workflows/ci-coach.lock.yml b/.github/workflows/ci-coach.lock.yml index 4756fdb8426..e88d5df1580 100644 --- a/.github/workflows/ci-coach.lock.yml +++ b/.github/workflows/ci-coach.lock.yml @@ -7338,6 +7338,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7628,7 +7630,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[ci-coach] " GH_AW_PR_DRAFT: "true" diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 82028c6f4ac..a49063de839 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -6849,6 +6849,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/cli-consistency-checker.lock.yml b/.github/workflows/cli-consistency-checker.lock.yml index 85d71206421..76525f632e6 100644 --- a/.github/workflows/cli-consistency-checker.lock.yml +++ b/.github/workflows/cli-consistency-checker.lock.yml @@ -6565,6 +6565,8 @@ jobs: contents: read issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index 1ecd0d14048..37db31b7c00 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -6410,6 +6410,8 @@ jobs: contents: read issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index ce3211ec2e0..d6eb4b99692 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -7515,6 +7515,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8384,7 +8386,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[cloclo] " GH_AW_PR_LABELS: "automation,cloclo" diff --git a/.github/workflows/close-old-discussions.lock.yml b/.github/workflows/close-old-discussions.lock.yml index 6edc53a5b33..da1e5bf93e1 100644 --- a/.github/workflows/close-old-discussions.lock.yml +++ b/.github/workflows/close-old-discussions.lock.yml @@ -6500,6 +6500,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" steps: - name: Download agent output artifact continue-on-error: true diff --git a/.github/workflows/commit-changes-analyzer.lock.yml b/.github/workflows/commit-changes-analyzer.lock.yml index b7c7872ce6a..88858631efd 100644 --- a/.github/workflows/commit-changes-analyzer.lock.yml +++ b/.github/workflows/commit-changes-analyzer.lock.yml @@ -6323,6 +6323,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index 0dec0c230c8..e3183bda3a9 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -6720,6 +6720,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/copilot-pr-merged-report.lock.yml b/.github/workflows/copilot-pr-merged-report.lock.yml index 4d35ae25274..094bd551573 100644 --- a/.github/workflows/copilot-pr-merged-report.lock.yml +++ b/.github/workflows/copilot-pr-merged-report.lock.yml @@ -7950,6 +7950,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index 690934e2e73..4b2a3e1e81b 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -7367,6 +7367,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 54bd59489bb..429b876cde4 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -6852,6 +6852,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index d15a9ab3c70..f8b6114c57b 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -7459,6 +7459,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index b8b2ab206ca..2d5f12a3bbb 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -7625,6 +7625,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/daily-assign-issue-to-user.lock.yml b/.github/workflows/daily-assign-issue-to-user.lock.yml index 53f7647403b..5c7ffbaa170 100644 --- a/.github/workflows/daily-assign-issue-to-user.lock.yml +++ b/.github/workflows/daily-assign-issue-to-user.lock.yml @@ -6360,6 +6360,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/daily-code-metrics.lock.yml b/.github/workflows/daily-code-metrics.lock.yml index 0074b05de5a..a86091e8821 100644 --- a/.github/workflows/daily-code-metrics.lock.yml +++ b/.github/workflows/daily-code-metrics.lock.yml @@ -6957,6 +6957,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/daily-copilot-token-report.lock.yml b/.github/workflows/daily-copilot-token-report.lock.yml index 97d6c3fb094..d8fe1145535 100644 --- a/.github/workflows/daily-copilot-token-report.lock.yml +++ b/.github/workflows/daily-copilot-token-report.lock.yml @@ -7456,6 +7456,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index 6c450b99447..772ca031de6 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -6259,6 +6259,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6549,7 +6551,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[docs] " GH_AW_PR_LABELS: "documentation,automation" diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml index 01919b88a6e..ae1d9a85c1d 100644 --- a/.github/workflows/daily-fact.lock.yml +++ b/.github/workflows/daily-fact.lock.yml @@ -6320,6 +6320,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/daily-file-diet.lock.yml b/.github/workflows/daily-file-diet.lock.yml index fa84c2bd1e7..b13fb009e18 100644 --- a/.github/workflows/daily-file-diet.lock.yml +++ b/.github/workflows/daily-file-diet.lock.yml @@ -7837,6 +7837,8 @@ jobs: contents: write issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml index 9a0f0ea5dac..d7ee89e55f9 100644 --- a/.github/workflows/daily-firewall-report.lock.yml +++ b/.github/workflows/daily-firewall-report.lock.yml @@ -7339,6 +7339,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/daily-issues-report.lock.yml b/.github/workflows/daily-issues-report.lock.yml index efe15957781..66e81da1668 100644 --- a/.github/workflows/daily-issues-report.lock.yml +++ b/.github/workflows/daily-issues-report.lock.yml @@ -7497,6 +7497,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/daily-malicious-code-scan.lock.yml b/.github/workflows/daily-malicious-code-scan.lock.yml index fde57408cfa..f851cd4e5c4 100644 --- a/.github/workflows/daily-malicious-code-scan.lock.yml +++ b/.github/workflows/daily-malicious-code-scan.lock.yml @@ -6428,6 +6428,8 @@ jobs: contents: read security-events: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" steps: - name: Download agent output artifact continue-on-error: true diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index 6e4030d44bc..a6f0ce2812c 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -6233,6 +6233,8 @@ jobs: contents: write issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index bb799ad9ccc..f0170d05f81 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -7261,6 +7261,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/daily-performance-summary.lock.yml b/.github/workflows/daily-performance-summary.lock.yml index 5c27fb464d8..0cbd6b3c20d 100644 --- a/.github/workflows/daily-performance-summary.lock.yml +++ b/.github/workflows/daily-performance-summary.lock.yml @@ -8960,6 +8960,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/daily-repo-chronicle.lock.yml b/.github/workflows/daily-repo-chronicle.lock.yml index c2cc0a25fc4..9534a2987aa 100644 --- a/.github/workflows/daily-repo-chronicle.lock.yml +++ b/.github/workflows/daily-repo-chronicle.lock.yml @@ -7182,6 +7182,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/daily-team-status.lock.yml b/.github/workflows/daily-team-status.lock.yml index b487e8e8a69..dd9541daf71 100644 --- a/.github/workflows/daily-team-status.lock.yml +++ b/.github/workflows/daily-team-status.lock.yml @@ -6470,6 +6470,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/daily-workflow-updater.lock.yml b/.github/workflows/daily-workflow-updater.lock.yml index 83af3b956bd..3380b8694d3 100644 --- a/.github/workflows/daily-workflow-updater.lock.yml +++ b/.github/workflows/daily-workflow-updater.lock.yml @@ -6529,6 +6529,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6819,7 +6821,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[actions] " GH_AW_PR_LABELS: "dependencies,automation" diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index c180aa6a34f..886370667d8 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -7181,6 +7181,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/dependabot-go-checker.lock.yml b/.github/workflows/dependabot-go-checker.lock.yml index 63a78fa1e01..fb5f682b3a7 100644 --- a/.github/workflows/dependabot-go-checker.lock.yml +++ b/.github/workflows/dependabot-go-checker.lock.yml @@ -6834,6 +6834,8 @@ jobs: contents: read issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index 10db14903d6..739aedeaf50 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -6659,6 +6659,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index 604cd4e68ec..c8e95f491e5 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -7721,6 +7721,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: update_discussion_discussion_number: ${{ steps.update_discussion.outputs.discussion_number }} update_discussion_discussion_url: ${{ steps.update_discussion.outputs.discussion_url }} diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index 4aba5dae17c..2094a49f487 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -6876,6 +6876,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8224,7 +8226,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[docs] " GH_AW_PR_LABELS: "documentation,automation" diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index d9c5d0198cc..e673f756ec8 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -6491,6 +6491,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6781,7 +6783,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[docs] " GH_AW_PR_LABELS: "documentation,automation" diff --git a/.github/workflows/docs-noob-tester.lock.yml b/.github/workflows/docs-noob-tester.lock.yml index 49a74717591..79e4d65afa3 100644 --- a/.github/workflows/docs-noob-tester.lock.yml +++ b/.github/workflows/docs-noob-tester.lock.yml @@ -6584,6 +6584,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index 2ff763a388e..a1d609a89a8 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -6548,6 +6548,8 @@ jobs: contents: read issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/example-workflow-analyzer.lock.yml b/.github/workflows/example-workflow-analyzer.lock.yml index 76f4c6d9850..9892a54f99b 100644 --- a/.github/workflows/example-workflow-analyzer.lock.yml +++ b/.github/workflows/example-workflow-analyzer.lock.yml @@ -6071,6 +6071,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/github-mcp-structural-analysis.lock.yml b/.github/workflows/github-mcp-structural-analysis.lock.yml index 75a78c1a6f5..830c26aaa41 100644 --- a/.github/workflows/github-mcp-structural-analysis.lock.yml +++ b/.github/workflows/github-mcp-structural-analysis.lock.yml @@ -6812,6 +6812,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index b03809e2162..fefed39a364 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -6687,6 +6687,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8035,7 +8037,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[mcp-tools] " GH_AW_PR_LABELS: "documentation,automation" diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index 21c4a6cc8bc..5ac8f83296d 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -7118,6 +7118,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7408,7 +7410,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[docs] " GH_AW_PR_LABELS: "documentation,glossary" diff --git a/.github/workflows/go-fan.lock.yml b/.github/workflows/go-fan.lock.yml index a109aab27cb..e13eaf67487 100644 --- a/.github/workflows/go-fan.lock.yml +++ b/.github/workflows/go-fan.lock.yml @@ -6467,6 +6467,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/go-file-size-reduction-project64.campaign.g.lock.yml b/.github/workflows/go-file-size-reduction-project64.campaign.g.lock.yml index d89e2fd8124..524a1b7ecd5 100644 --- a/.github/workflows/go-file-size-reduction-project64.campaign.g.lock.yml +++ b/.github/workflows/go-file-size-reduction-project64.campaign.g.lock.yml @@ -6556,6 +6556,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/go-file-size-reduction.campaign.g.lock.yml b/.github/workflows/go-file-size-reduction.campaign.g.lock.yml index b1158e8d2bf..5a98a7678cf 100644 --- a/.github/workflows/go-file-size-reduction.campaign.g.lock.yml +++ b/.github/workflows/go-file-size-reduction.campaign.g.lock.yml @@ -6556,6 +6556,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index 32fc9df0a81..6d4379e43db 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -6334,6 +6334,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6624,7 +6626,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[log] " GH_AW_PR_LABELS: "enhancement,automation" diff --git a/.github/workflows/go-pattern-detector.lock.yml b/.github/workflows/go-pattern-detector.lock.yml index dea4c0c3081..e250b883305 100644 --- a/.github/workflows/go-pattern-detector.lock.yml +++ b/.github/workflows/go-pattern-detector.lock.yml @@ -6198,6 +6198,8 @@ jobs: contents: read issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml index 20c848095fe..abaade10527 100644 --- a/.github/workflows/grumpy-reviewer.lock.yml +++ b/.github/workflows/grumpy-reviewer.lock.yml @@ -7556,6 +7556,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/hourly-ci-cleaner.lock.yml b/.github/workflows/hourly-ci-cleaner.lock.yml index c014f77599a..fe5937a6117 100644 --- a/.github/workflows/hourly-ci-cleaner.lock.yml +++ b/.github/workflows/hourly-ci-cleaner.lock.yml @@ -6755,6 +6755,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7045,7 +7047,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[ca] " GH_AW_PR_DRAFT: "true" diff --git a/.github/workflows/human-ai-collaboration.lock.yml b/.github/workflows/human-ai-collaboration.lock.yml index d369afc935f..a7affd49dde 100644 --- a/.github/workflows/human-ai-collaboration.lock.yml +++ b/.github/workflows/human-ai-collaboration.lock.yml @@ -7079,6 +7079,8 @@ jobs: contents: read issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/incident-response.lock.yml b/.github/workflows/incident-response.lock.yml index e87da72a550..8cfb834352b 100644 --- a/.github/workflows/incident-response.lock.yml +++ b/.github/workflows/incident-response.lock.yml @@ -7240,6 +7240,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -9263,7 +9265,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_LABELS: "campaign-fix,incident" GH_AW_PR_DRAFT: "true" diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index aba4a8a839a..5dd3357f5b8 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -6214,6 +6214,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6504,7 +6506,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[instructions] " GH_AW_PR_LABELS: "documentation,automation,instructions" diff --git a/.github/workflows/intelligence.lock.yml b/.github/workflows/intelligence.lock.yml index 628cd0724c3..829a030c5ad 100644 --- a/.github/workflows/intelligence.lock.yml +++ b/.github/workflows/intelligence.lock.yml @@ -7775,6 +7775,8 @@ jobs: contents: write issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/issue-arborist.lock.yml b/.github/workflows/issue-arborist.lock.yml index f7ce2e4713e..7ee87276ecd 100644 --- a/.github/workflows/issue-arborist.lock.yml +++ b/.github/workflows/issue-arborist.lock.yml @@ -6619,6 +6619,8 @@ jobs: discussions: write issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index 4b01e805831..23862aa51f8 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -5265,6 +5265,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_labels_labels_added: ${{ steps.add_labels.outputs.labels_added }} steps: diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index 621aae9ad09..195883a3ca0 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -6773,6 +6773,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/issue-triage-agent.lock.yml b/.github/workflows/issue-triage-agent.lock.yml index 0f56ea0343a..d69db29fb4a 100644 --- a/.github/workflows/issue-triage-agent.lock.yml +++ b/.github/workflows/issue-triage-agent.lock.yml @@ -6335,6 +6335,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/jsweep.lock.yml b/.github/workflows/jsweep.lock.yml index f324b738d55..1f5f5ff84c0 100644 --- a/.github/workflows/jsweep.lock.yml +++ b/.github/workflows/jsweep.lock.yml @@ -6627,6 +6627,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6917,7 +6919,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[jsweep] " GH_AW_PR_LABELS: "unbloat,automation" diff --git a/.github/workflows/layout-spec-maintainer.lock.yml b/.github/workflows/layout-spec-maintainer.lock.yml index 761a085bca2..915e16272ce 100644 --- a/.github/workflows/layout-spec-maintainer.lock.yml +++ b/.github/workflows/layout-spec-maintainer.lock.yml @@ -6647,6 +6647,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6937,7 +6939,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[specs] " GH_AW_PR_LABELS: "documentation,automation" diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index 44717a7ed04..1138d7692cc 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -6458,6 +6458,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index c33812a83c1..7b2d1960044 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -7284,6 +7284,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index 92edc0755fb..f723f33a4ec 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -7338,6 +7338,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: push_to_pull_request_branch_commit_url: ${{ steps.push_to_pull_request_branch.outputs.commit_url }} steps: diff --git a/.github/workflows/org-health-report.lock.yml b/.github/workflows/org-health-report.lock.yml index aae4a90d7f4..68257ba63ad 100644 --- a/.github/workflows/org-health-report.lock.yml +++ b/.github/workflows/org-health-report.lock.yml @@ -7293,6 +7293,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/org-wide-rollout.lock.yml b/.github/workflows/org-wide-rollout.lock.yml index 9b52c9f6777..8ad25564cd5 100644 --- a/.github/workflows/org-wide-rollout.lock.yml +++ b/.github/workflows/org-wide-rollout.lock.yml @@ -7268,6 +7268,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -9291,7 +9293,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_LABELS: "campaign-pr,org-rollout" GH_AW_PR_DRAFT: "true" diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 4773aca54f3..f6fe3867587 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -7548,6 +7548,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index 7a238dce4ff..179db50cdca 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -7579,6 +7579,8 @@ jobs: discussions: write issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index afd54319951..93897b60031 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -7994,6 +7994,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -11490,7 +11492,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[🎨 POETRY] " GH_AW_PR_LABELS: "poetry,automation,creative-writing" diff --git a/.github/workflows/portfolio-analyst.lock.yml b/.github/workflows/portfolio-analyst.lock.yml index 3e48348fb2e..15fdd5dfc0a 100644 --- a/.github/workflows/portfolio-analyst.lock.yml +++ b/.github/workflows/portfolio-analyst.lock.yml @@ -7488,6 +7488,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/pr-nitpick-reviewer.lock.yml b/.github/workflows/pr-nitpick-reviewer.lock.yml index ff1b16226e8..ece732630d7 100644 --- a/.github/workflows/pr-nitpick-reviewer.lock.yml +++ b/.github/workflows/pr-nitpick-reviewer.lock.yml @@ -7622,6 +7622,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index 645a745aed1..b1ca4a7b2b8 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -7093,6 +7093,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml index 2a1fbda6bbe..b147ee26230 100644 --- a/.github/workflows/python-data-charts.lock.yml +++ b/.github/workflows/python-data-charts.lock.yml @@ -7519,6 +7519,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index c113c6437d8..c33354b841b 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -7918,6 +7918,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8787,7 +8789,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[q] " GH_AW_PR_LABELS: "automation,workflow-optimization" diff --git a/.github/workflows/release.lock.yml b/.github/workflows/release.lock.yml index 2fc7cf1cc45..172f58f8a00 100644 --- a/.github/workflows/release.lock.yml +++ b/.github/workflows/release.lock.yml @@ -6743,6 +6743,8 @@ jobs: permissions: contents: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" steps: - name: Download agent output artifact continue-on-error: true diff --git a/.github/workflows/repo-tree-map.lock.yml b/.github/workflows/repo-tree-map.lock.yml index 1e194211869..531080de796 100644 --- a/.github/workflows/repo-tree-map.lock.yml +++ b/.github/workflows/repo-tree-map.lock.yml @@ -6516,6 +6516,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/repository-quality-improver.lock.yml b/.github/workflows/repository-quality-improver.lock.yml index a15ab7eb954..7e71bfc8555 100644 --- a/.github/workflows/repository-quality-improver.lock.yml +++ b/.github/workflows/repository-quality-improver.lock.yml @@ -7074,6 +7074,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/research.lock.yml b/.github/workflows/research.lock.yml index 8c62045fb49..265cca0298e 100644 --- a/.github/workflows/research.lock.yml +++ b/.github/workflows/research.lock.yml @@ -6484,6 +6484,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml index 9ed57f62d45..326458cf5e1 100644 --- a/.github/workflows/safe-output-health.lock.yml +++ b/.github/workflows/safe-output-health.lock.yml @@ -6627,6 +6627,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml index 3488b75160d..610091fecf0 100644 --- a/.github/workflows/schema-consistency-checker.lock.yml +++ b/.github/workflows/schema-consistency-checker.lock.yml @@ -6394,6 +6394,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 80cdd5fe182..82bd1a8558a 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -7480,6 +7480,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/security-compliance.lock.yml b/.github/workflows/security-compliance.lock.yml index b4b683f05f9..0e215ace9f9 100644 --- a/.github/workflows/security-compliance.lock.yml +++ b/.github/workflows/security-compliance.lock.yml @@ -6899,6 +6899,8 @@ jobs: contents: read issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml index cee0ee14b72..357c9dea666 100644 --- a/.github/workflows/security-fix-pr.lock.yml +++ b/.github/workflows/security-fix-pr.lock.yml @@ -6424,6 +6424,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6714,7 +6716,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[security-fix] " GH_AW_PR_LABELS: "security,automated-fix" diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index 95357d6502f..71d89a8a98b 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -6641,6 +6641,8 @@ jobs: contents: read issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/slide-deck-maintainer.lock.yml b/.github/workflows/slide-deck-maintainer.lock.yml index dd86243fe4c..185db14133a 100644 --- a/.github/workflows/slide-deck-maintainer.lock.yml +++ b/.github/workflows/slide-deck-maintainer.lock.yml @@ -6921,6 +6921,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7211,7 +7213,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[slides] " GH_AW_PR_DRAFT: "true" diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index d9a071f28dc..227f5ed7fbf 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -6929,6 +6929,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/smoke-codex-firewall.lock.yml b/.github/workflows/smoke-codex-firewall.lock.yml index e4901e6ce35..913095040d1 100644 --- a/.github/workflows/smoke-codex-firewall.lock.yml +++ b/.github/workflows/smoke-codex-firewall.lock.yml @@ -6986,6 +6986,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 4e7505819c1..68673ac4f58 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -7099,6 +7099,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/smoke-copilot-no-firewall.lock.yml b/.github/workflows/smoke-copilot-no-firewall.lock.yml index 9fa16b01d5e..a23e1ac6213 100644 --- a/.github/workflows/smoke-copilot-no-firewall.lock.yml +++ b/.github/workflows/smoke-copilot-no-firewall.lock.yml @@ -8279,6 +8279,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/smoke-copilot-playwright.lock.yml b/.github/workflows/smoke-copilot-playwright.lock.yml index 08c48d77781..1e140f84b18 100644 --- a/.github/workflows/smoke-copilot-playwright.lock.yml +++ b/.github/workflows/smoke-copilot-playwright.lock.yml @@ -8572,6 +8572,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/smoke-copilot-safe-inputs.lock.yml b/.github/workflows/smoke-copilot-safe-inputs.lock.yml index f44bc5f5add..0b19e4ddba3 100644 --- a/.github/workflows/smoke-copilot-safe-inputs.lock.yml +++ b/.github/workflows/smoke-copilot-safe-inputs.lock.yml @@ -8253,6 +8253,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 947e8139418..6fd78dbfbb4 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -7094,6 +7094,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/smoke-detector.lock.yml b/.github/workflows/smoke-detector.lock.yml index b7611e70b75..483ae841ed3 100644 --- a/.github/workflows/smoke-detector.lock.yml +++ b/.github/workflows/smoke-detector.lock.yml @@ -7042,6 +7042,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/spec-kit-execute.lock.yml b/.github/workflows/spec-kit-execute.lock.yml index 5a57d2c6b9e..15e33a3bf12 100644 --- a/.github/workflows/spec-kit-execute.lock.yml +++ b/.github/workflows/spec-kit-execute.lock.yml @@ -7023,6 +7023,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7313,7 +7315,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[spec-kit] " GH_AW_PR_LABELS: "spec-kit,automation" diff --git a/.github/workflows/spec-kit-executor.lock.yml b/.github/workflows/spec-kit-executor.lock.yml index 38a2f60fae6..52f1aaffafb 100644 --- a/.github/workflows/spec-kit-executor.lock.yml +++ b/.github/workflows/spec-kit-executor.lock.yml @@ -6898,6 +6898,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7188,7 +7190,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[spec-kit] " GH_AW_PR_LABELS: "spec-kit,automation" diff --git a/.github/workflows/speckit-dispatcher.lock.yml b/.github/workflows/speckit-dispatcher.lock.yml index 76341351f9a..de79f258175 100644 --- a/.github/workflows/speckit-dispatcher.lock.yml +++ b/.github/workflows/speckit-dispatcher.lock.yml @@ -7827,6 +7827,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml index fdb82a3281a..8444816f964 100644 --- a/.github/workflows/stale-repo-identifier.lock.yml +++ b/.github/workflows/stale-repo-identifier.lock.yml @@ -7448,6 +7448,8 @@ jobs: contents: write issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index f489591722d..bc75dc63e35 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -6478,6 +6478,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/sub-issue-closer.lock.yml b/.github/workflows/sub-issue-closer.lock.yml index cb84f95280c..d0073f1d86d 100644 --- a/.github/workflows/sub-issue-closer.lock.yml +++ b/.github/workflows/sub-issue-closer.lock.yml @@ -6506,6 +6506,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/.github/workflows/super-linter.lock.yml b/.github/workflows/super-linter.lock.yml index 0b962c3c44d..856fd1a0ca4 100644 --- a/.github/workflows/super-linter.lock.yml +++ b/.github/workflows/super-linter.lock.yml @@ -6644,6 +6644,8 @@ jobs: contents: read issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index 50524052275..7eeef8aee07 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -6927,6 +6927,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -7796,7 +7798,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[docs] " GH_AW_PR_LABELS: "documentation" diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 5e273cfc78e..42ca1e9d816 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -7144,6 +7144,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7473,7 +7475,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[tidy] " GH_AW_PR_LABELS: "automation,maintenance" diff --git a/.github/workflows/typist.lock.yml b/.github/workflows/typist.lock.yml index fdd539e06bf..9130ff5067a 100644 --- a/.github/workflows/typist.lock.yml +++ b/.github/workflows/typist.lock.yml @@ -6639,6 +6639,8 @@ jobs: contents: read discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 8b711ea515f..9d6f8128256 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -7196,6 +7196,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8065,7 +8067,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_ID: "agent" GH_AW_BASE_BRANCH: ${{ github.ref_name }} GH_AW_PR_TITLE_PREFIX: "[docs] " GH_AW_PR_LABELS: "documentation,automation" diff --git a/.github/workflows/video-analyzer.lock.yml b/.github/workflows/video-analyzer.lock.yml index e798b17227e..31f3f1d2ca7 100644 --- a/.github/workflows/video-analyzer.lock.yml +++ b/.github/workflows/video-analyzer.lock.yml @@ -6668,6 +6668,8 @@ jobs: contents: read issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} diff --git a/.github/workflows/weekly-issue-summary.lock.yml b/.github/workflows/weekly-issue-summary.lock.yml index a670d423b60..28d351827cb 100644 --- a/.github/workflows/weekly-issue-summary.lock.yml +++ b/.github/workflows/weekly-issue-summary.lock.yml @@ -7090,6 +7090,8 @@ jobs: contents: write discussions: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} diff --git a/.github/workflows/workflow-generator.lock.yml b/.github/workflows/workflow-generator.lock.yml index 106ea183b98..88a80536d03 100644 --- a/.github/workflows/workflow-generator.lock.yml +++ b/.github/workflows/workflow-generator.lock.yml @@ -6704,6 +6704,8 @@ jobs: contents: read issues: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: assign_to_agent_assigned: ${{ steps.assign_to_agent.outputs.assigned }} steps: diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index ef992194df4..ae97e5a61ba 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -2980,10 +2980,7 @@ jobs: return []; } } - function sanitizeContentCore(content, maxLength) { - if (!content || typeof content !== "string") { - return ""; - } + function buildAllowedDomains() { const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; let allowedDomains = allowedDomainsEnv @@ -3002,131 +2999,141 @@ jobs: const apiDomains = extractDomainsFromUrl(githubApiUrl); allowedDomains = allowedDomains.concat(apiDomains); } - allowedDomains = [...new Set(allowedDomains)]; - let sanitized = content; - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - sanitized = neutralizeCommands(sanitized); - sanitized = neutralizeAllMentions(sanitized); - sanitized = removeXmlComments(sanitized); - sanitized = convertXmlTags(sanitized); - sanitized = sanitizeUrlProtocols(sanitized); - sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + return [...new Set(allowedDomains)]; + } + function sanitizeUrlProtocols(s) { + return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { + if (domain) { + const domainLower = domain.toLowerCase(); + const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); + } + addRedactedDomain(domainLower); } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } - sanitized = neutralizeBotTriggers(sanitized); - return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; - return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + const protocolMatch = match.match(/^([^:]+):/); + if (protocolMatch) { + const protocol = protocolMatch[1] + ":"; + const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; if (typeof core !== "undefined" && core.info) { core.info(`Redacted URL: ${truncated}`); } if (typeof core !== "undefined" && core.debug) { core.debug(`Redacted URL (full): ${match}`); } - addRedactedDomain(hostname); - return "(redacted)"; + addRedactedDomain(protocol); } + } + return "(redacted)"; + }); + } + function sanitizeUrlDomains(s, allowed) { + const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi; + return s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { + const hostname = hostnameWithPort.split(":")[0].toLowerCase(); + pathPart = pathPart || ""; + const isAllowed = allowed.some(allowedDomain => { + const normalizedAllowed = allowedDomain.toLowerCase(); + if (hostname === normalizedAllowed) { + return true; + } + if (normalizedAllowed.startsWith("*.")) { + const baseDomain = normalizedAllowed.substring(2); + return hostname.endsWith("." + baseDomain) || hostname === baseDomain; + } + return hostname.endsWith("." + normalizedAllowed); }); - } - function sanitizeUrlProtocols(s) { - return s.replace(/((?:http|ftp|file|ssh|git):\/\/([\w.-]*)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } + if (isAllowed) { + return match; + } else { + const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; + if (typeof core !== "undefined" && core.info) { + core.info(`Redacted URL: ${truncated}`); + } + if (typeof core !== "undefined" && core.debug) { + core.debug(`Redacted URL (full): ${match}`); } + addRedactedDomain(hostname); return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } - function neutralizeAllMentions(s) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { - if (typeof core !== "undefined" && core.info) { - core.info(`Escaped mention: @${p2} (not in allowed list)`); - } - return `${p1}\`@${p2}\``; - }); - } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); + }); + } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + function neutralizeAllMentions(s) { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + if (typeof core !== "undefined" && core.info) { + core.info(`Escaped mention: @${p2} (not in allowed list)`); + } + return `${p1}\`@${p2}\``; + }); + } + function removeXmlComments(s) { + return s.replace(//g, "").replace(//g, ""); + } + function convertXmlTags(s) { + const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; } - return `(${tagContent})`; - }); + } + return `(${tagContent})`; + }); + } + function neutralizeBotTriggers(s) { + return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + } + function applyTruncation(content, maxLength) { + maxLength = maxLength || 524288; + const lines = content.split("\n"); + const maxLines = 65000; + if (lines.length > maxLines) { + const truncationMsg = "\n[Content truncated due to line count]"; + const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; + if (truncatedLines.length > maxLength) { + return truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; + } else { + return truncatedLines; + } + } else if (content.length > maxLength) { + return content.substring(0, maxLength) + "\n[Content truncated due to length]"; } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); + return content; + } + function sanitizeContentCore(content, maxLength) { + if (!content || typeof content !== "string") { + return ""; } + const allowedDomains = buildAllowedDomains(); + let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); + sanitized = neutralizeCommands(sanitized); + sanitized = neutralizeAllMentions(sanitized); + sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); + sanitized = sanitizeUrlProtocols(sanitized); + sanitized = sanitizeUrlDomains(sanitized, allowedDomains); + sanitized = applyTruncation(sanitized, maxLength); + sanitized = neutralizeBotTriggers(sanitized); + return sanitized.trim(); } function sanitizeContent(content, maxLengthOrOptions) { let maxLength; @@ -3143,119 +3150,19 @@ jobs: if (!content || typeof content !== "string") { return ""; } - const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS; - const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"]; - let allowedDomains = allowedDomainsEnv - ? allowedDomainsEnv - .split(",") - .map(d => d.trim()) - .filter(d => d) - : defaultAllowedDomains; - const githubServerUrl = process.env.GITHUB_SERVER_URL; - const githubApiUrl = process.env.GITHUB_API_URL; - if (githubServerUrl) { - const serverDomains = extractDomainsFromUrl(githubServerUrl); - allowedDomains = allowedDomains.concat(serverDomains); - } - if (githubApiUrl) { - const apiDomains = extractDomainsFromUrl(githubApiUrl); - allowedDomains = allowedDomains.concat(apiDomains); - } - allowedDomains = [...new Set(allowedDomains)]; + const allowedDomains = buildAllowedDomains(); let sanitized = content; + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase); sanitized = removeXmlComments(sanitized); sanitized = convertXmlTags(sanitized); - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); - sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); sanitized = sanitizeUrlDomains(sanitized, allowedDomains); - const lines = sanitized.split("\n"); - const maxLines = 65000; - maxLength = maxLength || 524288; - if (lines.length > maxLines) { - const truncationMsg = "\n[Content truncated due to line count]"; - const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg; - if (truncatedLines.length > maxLength) { - sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg; - } else { - sanitized = truncatedLines; - } - } else if (sanitized.length > maxLength) { - sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]"; - } + sanitized = applyTruncation(sanitized, maxLength); sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); - function sanitizeUrlDomains(s, allowed) { - const httpsUrlRegex = /https:\/\/([\w.-]+(?::\d+)?)(\/[^\s]*)?/gi; - const result = s.replace(httpsUrlRegex, (match, hostnameWithPort, pathPart) => { - const hostname = hostnameWithPort.split(":")[0].toLowerCase(); - pathPart = pathPart || ""; - const isAllowed = allowed.some(allowedDomain => { - const normalizedAllowed = allowedDomain.toLowerCase(); - if (hostname === normalizedAllowed) { - return true; - } - if (normalizedAllowed.startsWith("*.")) { - const baseDomain = normalizedAllowed.substring(2); - return hostname.endsWith("." + baseDomain) || hostname === baseDomain; - } - return hostname.endsWith("." + normalizedAllowed); - }); - if (isAllowed) { - return match; - } else { - const truncated = hostname.length > 12 ? hostname.substring(0, 12) + "..." : hostname; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(hostname); - return "(redacted)"; - } - }); - return result; - } - function sanitizeUrlProtocols(s) { - return s.replace(/\b((?:http|ftp|file|ssh|git):\/\/([\w.-]+)(?:[^\s]*)|(?:data|javascript|vbscript|about|mailto|tel):[^\s]+)/gi, (match, _fullMatch, domain) => { - if (domain) { - const domainLower = domain.toLowerCase(); - const truncated = domainLower.length > 12 ? domainLower.substring(0, 12) + "..." : domainLower; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(domainLower); - } else { - const protocolMatch = match.match(/^([^:]+):/); - if (protocolMatch) { - const protocol = protocolMatch[1] + ":"; - const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match; - if (typeof core !== "undefined" && core.info) { - core.info(`Redacted URL: ${truncated}`); - } - if (typeof core !== "undefined" && core.debug) { - core.debug(`Redacted URL (full): ${match}`); - } - addRedactedDomain(protocol); - } - } - return "(redacted)"; - }); - } - function neutralizeCommands(s) { - const commandName = process.env.GH_AW_COMMAND; - if (!commandName) { - return s; - } - const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); - } function neutralizeMentions(s, allowedLowercase) { return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { const isAllowed = allowedLowercase.includes(p2.toLowerCase()); @@ -3268,29 +3175,6 @@ jobs: return `${p1}\`@${p2}\``; }); } - function removeXmlComments(s) { - return s.replace(//g, "").replace(//g, ""); - } - function convertXmlTags(s) { - const allowedTags = ["b", "blockquote", "br", "code", "details", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "li", "ol", "p", "pre", "strong", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", "tr", "ul"]; - s = s.replace(//g, (match, content) => { - const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); - return `(![CDATA[${convertedContent}]])`; - }); - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { - const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); - if (tagNameMatch) { - const tagName = tagNameMatch[1].toLowerCase(); - if (allowedTags.includes(tagName)) { - return match; - } - } - return `(${tagContent})`; - }); - } - function neutralizeBotTriggers(s) { - return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); - } } const crypto = require("crypto"); const TEMPORARY_ID_PATTERN = /#(aw_[0-9a-f]{12})/gi; @@ -7303,6 +7187,8 @@ jobs: issues: write pull-requests: write timeout-minutes: 15 + env: + GH_AW_WORKFLOW_ID: "agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} diff --git a/pkg/workflow/create_pull_request_env_vars_test.go b/pkg/workflow/create_pull_request_env_vars_test.go index 7e50eba97da..fb16e602bce 100644 --- a/pkg/workflow/create_pull_request_env_vars_test.go +++ b/pkg/workflow/create_pull_request_env_vars_test.go @@ -34,8 +34,8 @@ func TestCreatePullRequestStepConfigEnvVars(t *testing.T) { envVarsContent := strings.Join(stepConfig.CustomEnvVars, "") // Verify required environment variables are present + // Note: GH_AW_WORKFLOW_ID is now set at job level, not in step-level CustomEnvVars requiredEnvVars := map[string]string{ - "GH_AW_WORKFLOW_ID": `GH_AW_WORKFLOW_ID: "main_job"`, "GH_AW_BASE_BRANCH": "GH_AW_BASE_BRANCH: ${{ github.ref_name }}", "GH_AW_PR_TITLE_PREFIX": `GH_AW_PR_TITLE_PREFIX: "[TEST] "`, "GH_AW_PR_LABELS": `GH_AW_PR_LABELS: "automated,test"`, @@ -84,8 +84,8 @@ func TestCreatePullRequestStepConfigDefaultValues(t *testing.T) { envVarsContent := strings.Join(stepConfig.CustomEnvVars, "") // Verify default values + // Note: GH_AW_WORKFLOW_ID is now set at job level, not in step-level CustomEnvVars defaultEnvVars := map[string]string{ - "GH_AW_WORKFLOW_ID": `GH_AW_WORKFLOW_ID: "agent"`, "GH_AW_BASE_BRANCH": "GH_AW_BASE_BRANCH: ${{ github.ref_name }}", "GH_AW_PR_DRAFT": `GH_AW_PR_DRAFT: "true"`, // Default is true "GH_AW_PR_IF_NO_CHANGES": `GH_AW_PR_IF_NO_CHANGES: "warn"`, // Default is warn From 10c95bd387c4ebe0c6d64dcce5b38fa9ab701be4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 13:50:52 +0000 Subject: [PATCH 5/7] Use workflow filename for GH_AW_WORKFLOW_ID and move common env vars to job level - Changed GH_AW_WORKFLOW_ID from mainJobName to workflow filename (without extension) - Moved common environment variables to job level: GH_AW_WORKFLOW_NAME, GH_AW_WORKFLOW_SOURCE, GH_AW_TRACKER_ID, GH_AW_ENGINE_ID, GH_AW_ENGINE_VERSION, GH_AW_ENGINE_MODEL, GH_AW_SAFE_OUTPUTS_STAGED, GH_AW_TARGET_REPO_SLUG, GH_AW_SAFE_OUTPUT_MESSAGES - Added buildJobLevelSafeOutputEnvVars() to build job-level environment variables - Added buildStepLevelSafeOutputEnvVars() to build step-level env vars (excluding job-level ones) - Updated all consolidated step configs to use buildStepLevelSafeOutputEnvVars() - Individual job builders still use buildStandardSafeOutputEnvVars() for backward compatibility Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../compiler_safe_outputs_consolidated.go | 125 +++++++++++++----- pkg/workflow/safe_outputs_env.go | 31 +++++ 2 files changed, 126 insertions(+), 30 deletions(-) diff --git a/pkg/workflow/compiler_safe_outputs_consolidated.go b/pkg/workflow/compiler_safe_outputs_consolidated.go index f61bc479e48..9cf0ee55bdb 100644 --- a/pkg/workflow/compiler_safe_outputs_consolidated.go +++ b/pkg/workflow/compiler_safe_outputs_consolidated.go @@ -498,18 +498,22 @@ func (c *Compiler) buildConsolidatedSafeOutputsJob(data *WorkflowData, mainJobNa needs = append(needs, constants.ActivationJobName) } + // Extract workflow filename (without extension) for GH_AW_WORKFLOW_ID + workflowFilename := strings.TrimSuffix(filepath.Base(markdownPath), ".md") + + // Build job-level environment variables that are common to all safe output steps + jobEnv := c.buildJobLevelSafeOutputEnvVars(data, workflowFilename) + job := &Job{ Name: "safe_outputs", If: jobCondition.Render(), RunsOn: c.formatSafeOutputsRunsOn(data.SafeOutputs), Permissions: permissions.RenderToYAML(), TimeoutMinutes: 15, // Slightly longer timeout for consolidated job with multiple steps - Env: map[string]string{ - "GH_AW_WORKFLOW_ID": fmt.Sprintf("%q", mainJobName), - }, - Steps: steps, - Outputs: outputs, - Needs: needs, + Env: jobEnv, + Steps: steps, + Outputs: outputs, + Needs: needs, } consolidatedSafeOutputsLog.Printf("Built consolidated safe outputs job with %d steps", len(safeOutputStepNames)) @@ -581,6 +585,67 @@ func (c *Compiler) buildConsolidatedSafeOutputStep(data *WorkflowData, config Sa return steps } +// buildJobLevelSafeOutputEnvVars builds environment variables that should be set at the job level +// for the consolidated safe_outputs job. These are variables that are common to all safe output steps. +func (c *Compiler) buildJobLevelSafeOutputEnvVars(data *WorkflowData, workflowFilename string) map[string]string { + envVars := make(map[string]string) + + // Set GH_AW_WORKFLOW_ID to the workflow filename (without extension) + // This is used for branch naming in create_pull_request and other operations + envVars["GH_AW_WORKFLOW_ID"] = fmt.Sprintf("%q", workflowFilename) + + // Add workflow metadata that's common to all steps + envVars["GH_AW_WORKFLOW_NAME"] = fmt.Sprintf("%q", data.Name) + + if data.Source != "" { + envVars["GH_AW_WORKFLOW_SOURCE"] = fmt.Sprintf("%q", data.Source) + sourceURL := buildSourceURL(data.Source) + if sourceURL != "" { + envVars["GH_AW_WORKFLOW_SOURCE_URL"] = fmt.Sprintf("%q", sourceURL) + } + } + + if data.TrackerID != "" { + envVars["GH_AW_TRACKER_ID"] = fmt.Sprintf("%q", data.TrackerID) + } + + // Add engine metadata that's common to all steps + if data.EngineConfig != nil { + if data.EngineConfig.ID != "" { + envVars["GH_AW_ENGINE_ID"] = fmt.Sprintf("%q", data.EngineConfig.ID) + } + if data.EngineConfig.Version != "" { + envVars["GH_AW_ENGINE_VERSION"] = fmt.Sprintf("%q", data.EngineConfig.Version) + } + if data.EngineConfig.Model != "" { + envVars["GH_AW_ENGINE_MODEL"] = fmt.Sprintf("%q", data.EngineConfig.Model) + } + } + + // Add safe output job environment variables (staged/target repo) + if c.trialMode || data.SafeOutputs.Staged { + envVars["GH_AW_SAFE_OUTPUTS_STAGED"] = "\"true\"" + } + + // Set GH_AW_TARGET_REPO_SLUG - prefer trial target repo (applies to all steps) + // Note: Individual steps with target-repo config will override this in their step-level env + if c.trialMode && c.trialLogicalRepoSlug != "" { + envVars["GH_AW_TARGET_REPO_SLUG"] = fmt.Sprintf("%q", c.trialLogicalRepoSlug) + } + + // Add messages config if present (applies to all steps) + if data.SafeOutputs.Messages != nil { + messagesJSON, err := serializeMessagesConfig(data.SafeOutputs.Messages) + if err != nil { + consolidatedSafeOutputsLog.Printf("Warning: failed to serialize messages config: %v", err) + } else if messagesJSON != "" { + envVars["GH_AW_SAFE_OUTPUT_MESSAGES"] = fmt.Sprintf("%q", messagesJSON) + } + } + + return envVars +} + // buildDetectionSuccessCondition builds the condition to check if detection passed func buildDetectionSuccessCondition() ConditionNode { return BuildEquals( @@ -603,7 +668,7 @@ func (c *Compiler) buildCreateIssueStepConfig(data *WorkflowData, mainJobName st if cfg.Expires > 0 { customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_ISSUE_EXPIRES: \"%d\"\n", cfg.Expires)) } - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) condition := BuildSafeOutputType("create_issue") @@ -622,7 +687,7 @@ func (c *Compiler) buildCreateDiscussionStepConfig(data *WorkflowData, mainJobNa cfg := data.SafeOutputs.CreateDiscussions var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("create_discussion") @@ -676,7 +741,7 @@ func (c *Compiler) buildCreatePullRequestStepConfig(data *WorkflowData, mainJobN if cfg.Expires > 0 && cfg.TargetRepoSlug == "" { customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_PR_EXPIRES: \"%d\"\n", cfg.Expires)) } - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) condition := BuildSafeOutputType("create_pull_request") @@ -723,7 +788,7 @@ func (c *Compiler) buildAddCommentStepConfig(data *WorkflowData, mainJobName str customEnvVars = append(customEnvVars, " GH_AW_CREATED_PULL_REQUEST_URL: ${{ steps.create_pull_request.outputs.pull_request_url }}\n") customEnvVars = append(customEnvVars, " GH_AW_CREATED_PULL_REQUEST_NUMBER: ${{ steps.create_pull_request.outputs.pull_request_number }}\n") } - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) condition := BuildSafeOutputType("add_comment") @@ -742,7 +807,7 @@ func (c *Compiler) buildCloseDiscussionStepConfig(data *WorkflowData, mainJobNam cfg := data.SafeOutputs.CloseDiscussions var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("close_discussion") @@ -761,7 +826,7 @@ func (c *Compiler) buildCloseIssueStepConfig(data *WorkflowData, mainJobName str cfg := data.SafeOutputs.CloseIssues var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("close_issue") @@ -780,7 +845,7 @@ func (c *Compiler) buildClosePullRequestStepConfig(data *WorkflowData, mainJobNa cfg := data.SafeOutputs.ClosePullRequests var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("close_pull_request") @@ -807,7 +872,7 @@ func (c *Compiler) buildCreatePRReviewCommentStepConfig(data *WorkflowData, main if cfg.Target != "" { customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_PR_REVIEW_COMMENT_TARGET: %q\n", cfg.Target)) } - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("create_pull_request_review_comment") @@ -827,7 +892,7 @@ func (c *Compiler) buildCreateCodeScanningAlertStepConfig(data *WorkflowData, ma var customEnvVars []string customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_WORKFLOW_FILENAME: %q\n", workflowFilename)) - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("create_code_scanning_alert") @@ -853,7 +918,7 @@ func (c *Compiler) buildAddLabelsStepConfig(data *WorkflowData, mainJobName stri if cfg.Target != "" { customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_LABELS_TARGET: %q\n", cfg.Target)) } - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) condition := BuildSafeOutputType("add_labels") @@ -872,7 +937,7 @@ func (c *Compiler) buildAddReviewerStepConfig(data *WorkflowData, mainJobName st cfg := data.SafeOutputs.AddReviewer var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("add_reviewer") @@ -891,7 +956,7 @@ func (c *Compiler) buildAssignMilestoneStepConfig(data *WorkflowData, mainJobNam cfg := data.SafeOutputs.AssignMilestone var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("assign_milestone") @@ -910,7 +975,7 @@ func (c *Compiler) buildAssignToAgentStepConfig(data *WorkflowData, mainJobName cfg := data.SafeOutputs.AssignToAgent var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("assign_to_agent") @@ -930,7 +995,7 @@ func (c *Compiler) buildAssignToUserStepConfig(data *WorkflowData, mainJobName s cfg := data.SafeOutputs.AssignToUser var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("assign_to_user") @@ -949,7 +1014,7 @@ func (c *Compiler) buildUpdateIssueStepConfig(data *WorkflowData, mainJobName st cfg := data.SafeOutputs.UpdateIssues var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) condition := BuildSafeOutputType("update_issue") @@ -968,7 +1033,7 @@ func (c *Compiler) buildUpdatePullRequestStepConfig(data *WorkflowData, mainJobN cfg := data.SafeOutputs.UpdatePullRequests var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) condition := BuildSafeOutputType("update_pull_request") @@ -987,7 +1052,7 @@ func (c *Compiler) buildUpdateDiscussionStepConfig(data *WorkflowData, mainJobNa cfg := data.SafeOutputs.UpdateDiscussions var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, cfg.TargetRepoSlug)...) // Add target environment variable if set if cfg.Target != "" { @@ -1044,7 +1109,7 @@ func (c *Compiler) buildPushToPullRequestBranchStepConfig(data *WorkflowData, ma maxPatchSize = data.SafeOutputs.MaximumPatchSize } customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_MAX_PATCH_SIZE: %d\n", maxPatchSize)) - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("push_to_pull_request_branch") @@ -1067,7 +1132,7 @@ func (c *Compiler) buildUploadAssetsStepConfig(data *WorkflowData, mainJobName s cfg := data.SafeOutputs.UploadAssets var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("upload_asset") @@ -1086,7 +1151,7 @@ func (c *Compiler) buildUpdateReleaseStepConfig(data *WorkflowData, mainJobName cfg := data.SafeOutputs.UpdateRelease var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("update_release") @@ -1109,7 +1174,7 @@ func (c *Compiler) buildLinkSubIssueStepConfig(data *WorkflowData, mainJobName s customEnvVars = append(customEnvVars, " GH_AW_CREATED_ISSUE_NUMBER: ${{ steps.create_issue.outputs.issue_number }}\n") customEnvVars = append(customEnvVars, " GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }}\n") } - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("link_sub_issue") @@ -1128,7 +1193,7 @@ func (c *Compiler) buildHideCommentStepConfig(data *WorkflowData, mainJobName st cfg := data.SafeOutputs.HideComment var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("hide_comment") @@ -1147,7 +1212,7 @@ func (c *Compiler) buildCreateAgentTaskStepConfig(data *WorkflowData, mainJobNam cfg := data.SafeOutputs.CreateAgentTasks var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("create_agent_task") @@ -1166,7 +1231,7 @@ func (c *Compiler) buildUpdateProjectStepConfig(data *WorkflowData, mainJobName cfg := data.SafeOutputs.UpdateProjects var customEnvVars []string - customEnvVars = append(customEnvVars, c.buildStandardSafeOutputEnvVars(data, "")...) + customEnvVars = append(customEnvVars, c.buildStepLevelSafeOutputEnvVars(data, "")...) condition := BuildSafeOutputType("update_project") diff --git a/pkg/workflow/safe_outputs_env.go b/pkg/workflow/safe_outputs_env.go index 837c7dab422..67e71537653 100644 --- a/pkg/workflow/safe_outputs_env.go +++ b/pkg/workflow/safe_outputs_env.go @@ -148,6 +148,37 @@ func (c *Compiler) buildStandardSafeOutputEnvVars(data *WorkflowData, targetRepo return customEnvVars } +// buildStepLevelSafeOutputEnvVars builds environment variables for consolidated safe output steps +// This excludes variables that are already set at the job level in consolidated jobs +func (c *Compiler) buildStepLevelSafeOutputEnvVars(data *WorkflowData, targetRepoSlug string) []string { + var customEnvVars []string + + // Only add target repo slug if it's different from the job-level setting + // (i.e., this step has a specific target-repo config that overrides the global trial mode target) + if targetRepoSlug != "" { + // Step-specific target repo overrides job-level setting + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_TARGET_REPO_SLUG: %q\n", targetRepoSlug)) + } else if !c.trialMode && data.SafeOutputs.Staged { + // Step needs staged flag but there's no job-level target repo (not in trial mode) + // Job level only sets this if trialMode is true + customEnvVars = append(customEnvVars, " GH_AW_SAFE_OUTPUTS_STAGED: \"true\"\n") + } + + // Note: The following are now set at job level and should NOT be included here: + // - GH_AW_WORKFLOW_NAME + // - GH_AW_WORKFLOW_SOURCE + // - GH_AW_WORKFLOW_SOURCE_URL + // - GH_AW_TRACKER_ID + // - GH_AW_ENGINE_ID + // - GH_AW_ENGINE_VERSION + // - GH_AW_ENGINE_MODEL + // - GH_AW_SAFE_OUTPUTS_STAGED (if in trial mode) + // - GH_AW_TARGET_REPO_SLUG (if in trial mode and no step override) + // - GH_AW_SAFE_OUTPUT_MESSAGES + + return customEnvVars +} + // buildEngineMetadataEnvVars builds engine metadata environment variables (id, version, model) // These are used by the JavaScript footer generation to create XML comment markers for traceability func buildEngineMetadataEnvVars(engineConfig *EngineConfig) []string { From fb7c96f524e63c5dd329a0ca75ae03a580a3c554 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 13:52:09 +0000 Subject: [PATCH 6/7] Recompile all workflows with updated job-level environment variables - Workflow lock files now have common env vars at job level instead of duplicated in steps - GH_AW_WORKFLOW_ID now uses workflow filename instead of job name - 118/123 workflows compiled successfully (5 pre-existing failures) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/ai-moderator.lock.yml | 11 ++-- .github/workflows/archie.lock.yml | 8 +-- .github/workflows/artifacts-summary.lock.yml | 6 +- .github/workflows/audit-workflows.lock.yml | 11 ++-- .github/workflows/blog-auditor.lock.yml | 8 +-- .github/workflows/brave.lock.yml | 8 +-- .../breaking-change-checker.lock.yml | 10 ++-- .github/workflows/campaign-generator.lock.yml | 8 +-- .github/workflows/changeset.lock.yml | 11 ++-- .github/workflows/ci-coach.lock.yml | 8 +-- .github/workflows/ci-doctor.lock.yml | 17 ++---- .../cli-consistency-checker.lock.yml | 6 +- .../workflows/cli-version-checker.lock.yml | 6 +- .github/workflows/cloclo.lock.yml | 11 ++-- .../workflows/close-old-discussions.lock.yml | 6 +- .../commit-changes-analyzer.lock.yml | 6 +- .../workflows/copilot-agent-analysis.lock.yml | 6 +- .../copilot-pr-merged-report.lock.yml | 6 +- .../copilot-pr-nlp-analysis.lock.yml | 8 +-- .../copilot-pr-prompt-analysis.lock.yml | 6 +- .../copilot-session-insights.lock.yml | 8 +-- .github/workflows/craft.lock.yml | 11 ++-- .../daily-assign-issue-to-user.lock.yml | 8 +-- .github/workflows/daily-code-metrics.lock.yml | 8 +-- .../daily-copilot-token-report.lock.yml | 11 ++-- .github/workflows/daily-doc-updater.lock.yml | 8 +-- .github/workflows/daily-fact.lock.yml | 12 ++-- .github/workflows/daily-file-diet.lock.yml | 11 ++-- .../workflows/daily-firewall-report.lock.yml | 11 ++-- .../workflows/daily-issues-report.lock.yml | 14 ++--- .../daily-malicious-code-scan.lock.yml | 8 +-- .../daily-multi-device-docs-tester.lock.yml | 11 ++-- .github/workflows/daily-news.lock.yml | 11 ++-- .../daily-performance-summary.lock.yml | 14 ++--- .../workflows/daily-repo-chronicle.lock.yml | 11 ++-- .github/workflows/daily-team-status.lock.yml | 12 ++-- .../workflows/daily-workflow-updater.lock.yml | 8 +-- .github/workflows/deep-report.lock.yml | 11 ++-- .../workflows/dependabot-go-checker.lock.yml | 8 +-- .github/workflows/dev-hawk.lock.yml | 8 +-- .github/workflows/dev.lock.yml | 8 +-- .../developer-docs-consolidator.lock.yml | 8 +-- .github/workflows/dictation-prompt.lock.yml | 6 +- .github/workflows/docs-noob-tester.lock.yml | 8 +-- .../duplicate-code-detector.lock.yml | 6 +- .../example-workflow-analyzer.lock.yml | 6 +- .../github-mcp-structural-analysis.lock.yml | 8 +-- .../github-mcp-tools-report.lock.yml | 8 +-- .../workflows/glossary-maintainer.lock.yml | 6 +- .github/workflows/go-fan.lock.yml | 8 +-- ...ze-reduction-project64.campaign.g.lock.yml | 8 +-- ...go-file-size-reduction.campaign.g.lock.yml | 8 +-- .github/workflows/go-logger.lock.yml | 6 +- .../workflows/go-pattern-detector.lock.yml | 6 +- .github/workflows/grumpy-reviewer.lock.yml | 11 ++-- .github/workflows/hourly-ci-cleaner.lock.yml | 8 +-- .../workflows/human-ai-collaboration.lock.yml | 6 +- .github/workflows/incident-response.lock.yml | 12 +--- .../workflows/instructions-janitor.lock.yml | 6 +- .github/workflows/intelligence.lock.yml | 8 +-- .github/workflows/issue-arborist.lock.yml | 10 +--- .github/workflows/issue-classifier.lock.yml | 6 +- .github/workflows/issue-monster.lock.yml | 11 ++-- .github/workflows/issue-triage-agent.lock.yml | 8 +-- .github/workflows/jsweep.lock.yml | 8 +-- .../workflows/layout-spec-maintainer.lock.yml | 8 +-- .github/workflows/lockfile-stats.lock.yml | 6 +- .github/workflows/mcp-inspector.lock.yml | 6 +- .github/workflows/mergefest.lock.yml | 6 +- .github/workflows/org-health-report.lock.yml | 8 +-- .github/workflows/org-wide-rollout.lock.yml | 12 +--- .github/workflows/pdf-summary.lock.yml | 8 +-- .github/workflows/plan.lock.yml | 8 +-- .github/workflows/poem-bot.lock.yml | 55 ++----------------- .github/workflows/portfolio-analyst.lock.yml | 11 ++-- .../workflows/pr-nitpick-reviewer.lock.yml | 14 ++--- .../prompt-clustering-analysis.lock.yml | 6 +- .github/workflows/python-data-charts.lock.yml | 8 +-- .github/workflows/q.lock.yml | 11 ++-- .github/workflows/release.lock.yml | 6 +- .github/workflows/repo-tree-map.lock.yml | 6 +- .../repository-quality-improver.lock.yml | 6 +- .github/workflows/research.lock.yml | 6 +- .github/workflows/safe-output-health.lock.yml | 6 +- .../schema-consistency-checker.lock.yml | 6 +- .github/workflows/scout.lock.yml | 8 +-- .../workflows/security-compliance.lock.yml | 6 +- .github/workflows/security-fix-pr.lock.yml | 6 +- .../semantic-function-refactor.lock.yml | 8 +-- .../workflows/slide-deck-maintainer.lock.yml | 8 +-- .github/workflows/smoke-claude.lock.yml | 14 ++--- .../workflows/smoke-codex-firewall.lock.yml | 17 ++---- .github/workflows/smoke-codex.lock.yml | 17 ++---- .../smoke-copilot-no-firewall.lock.yml | 11 ++-- .../smoke-copilot-playwright.lock.yml | 14 ++--- .../smoke-copilot-safe-inputs.lock.yml | 8 +-- .github/workflows/smoke-copilot.lock.yml | 14 ++--- .github/workflows/smoke-detector.lock.yml | 11 ++-- .github/workflows/spec-kit-execute.lock.yml | 8 +-- .github/workflows/spec-kit-executor.lock.yml | 8 +-- .github/workflows/speckit-dispatcher.lock.yml | 14 ++--- .../workflows/stale-repo-identifier.lock.yml | 11 ++-- .../workflows/static-analysis-report.lock.yml | 6 +- .github/workflows/sub-issue-closer.lock.yml | 8 +-- .github/workflows/super-linter.lock.yml | 6 +- .../workflows/technical-doc-writer.lock.yml | 14 ++--- .github/workflows/tidy.lock.yml | 8 +-- .github/workflows/typist.lock.yml | 6 +- .github/workflows/unbloat-docs.lock.yml | 14 ++--- .github/workflows/video-analyzer.lock.yml | 6 +- .../workflows/weekly-issue-summary.lock.yml | 11 ++-- .github/workflows/workflow-generator.lock.yml | 8 +-- 112 files changed, 400 insertions(+), 638 deletions(-) diff --git a/.github/workflows/ai-moderator.lock.yml b/.github/workflows/ai-moderator.lock.yml index f98a8fff140..37fdfb3530e 100644 --- a/.github/workflows/ai-moderator.lock.yml +++ b/.github/workflows/ai-moderator.lock.yml @@ -6551,7 +6551,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: "gpt-5-mini" + GH_AW_WORKFLOW_ID: "ai-moderator" + GH_AW_WORKFLOW_NAME: "AI Moderator" outputs: add_labels_labels_added: ${{ steps.add_labels.outputs.labels_added }} steps: @@ -7341,9 +7344,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_LABELS_ALLOWED: "spam,ai-generated,link-spam,ai-inspected" GH_AW_LABELS_TARGET: "*" - GH_AW_WORKFLOW_NAME: "AI Moderator" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5-mini" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -7464,9 +7464,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "AI Moderator" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5-mini" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index 8c75f35788c..3ca4d5cb6f8 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -7559,7 +7559,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📊 *Diagram rendered by [{workflow_name}]({run_url})*\",\"runStarted\":\"📐 Archie here! [{workflow_name}]({run_url}) is sketching the architecture on this {event_type}...\",\"runSuccess\":\"🎨 Blueprint complete! [{workflow_name}]({run_url}) has visualized the connections. The architecture speaks for itself! ✅\",\"runFailure\":\"📐 Drafting interrupted! [{workflow_name}]({run_url}) {status}. The diagram remains incomplete...\"}" + GH_AW_WORKFLOW_ID: "archie" + GH_AW_WORKFLOW_NAME: "Archie" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8186,9 +8189,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Archie" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📊 *Diagram rendered by [{workflow_name}]({run_url})*\",\"runStarted\":\"📐 Archie here! [{workflow_name}]({run_url}) is sketching the architecture on this {event_type}...\",\"runSuccess\":\"🎨 Blueprint complete! [{workflow_name}]({run_url}) has visualized the connections. The architecture speaks for itself! ✅\",\"runFailure\":\"📐 Drafting interrupted! [{workflow_name}]({run_url}) {status}. The diagram remains incomplete...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml index 99a7e9d960b..186f84f8d08 100644 --- a/.github/workflows/artifacts-summary.lock.yml +++ b/.github/workflows/artifacts-summary.lock.yml @@ -6540,7 +6540,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "artifacts-summary" + GH_AW_WORKFLOW_NAME: "Artifacts Summary" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7453,8 +7455,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Artifacts Summary" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ steps.app-token.outputs.token }} script: | diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 7757f7c2c3a..70b3fd9a746 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -6922,7 +6922,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_TRACKER_ID: "audit-workflows-daily" + GH_AW_WORKFLOW_ID: "audit-workflows" + GH_AW_WORKFLOW_NAME: "Agentic Workflow Audit Agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7825,9 +7828,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Agentic Workflow Audit Agent" - GH_AW_TRACKER_ID: "audit-workflows-daily" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8112,9 +8112,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Agentic Workflow Audit Agent" - GH_AW_TRACKER_ID: "audit-workflows-daily" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/blog-auditor.lock.yml b/.github/workflows/blog-auditor.lock.yml index 00838f922f8..9a8d4381a46 100644 --- a/.github/workflows/blog-auditor.lock.yml +++ b/.github/workflows/blog-auditor.lock.yml @@ -6405,7 +6405,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_TRACKER_ID: "blog-auditor-weekly" + GH_AW_WORKFLOW_ID: "blog-auditor" + GH_AW_WORKFLOW_NAME: "Blog Auditor" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7308,9 +7311,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Blog Auditor" - GH_AW_TRACKER_ID: "blog-auditor-weekly" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index f3968c3d954..fd9ed2439db 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -7438,7 +7438,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🦁 *Search results brought to you by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔍 Brave Search activated! [{workflow_name}]({run_url}) is venturing into the web on this {event_type}...\",\"runSuccess\":\"🦁 Mission accomplished! [{workflow_name}]({run_url}) has returned with the findings. Knowledge acquired! 🏆\",\"runFailure\":\"🔍 Search interrupted! [{workflow_name}]({run_url}) {status}. The web remains unexplored...\"}" + GH_AW_WORKFLOW_ID: "brave" + GH_AW_WORKFLOW_NAME: "Brave Web Search Agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8065,9 +8068,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Brave Web Search Agent" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🦁 *Search results brought to you by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔍 Brave Search activated! [{workflow_name}]({run_url}) is venturing into the web on this {event_type}...\",\"runSuccess\":\"🦁 Mission accomplished! [{workflow_name}]({run_url}) has returned with the findings. Knowledge acquired! 🏆\",\"runFailure\":\"🔍 Search interrupted! [{workflow_name}]({run_url}) {status}. The web remains unexplored...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/breaking-change-checker.lock.yml b/.github/workflows/breaking-change-checker.lock.yml index a3bc186e5fb..7c345ba111b 100644 --- a/.github/workflows/breaking-change-checker.lock.yml +++ b/.github/workflows/breaking-change-checker.lock.yml @@ -6774,7 +6774,11 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e ⚠️ *Compatibility report by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔬 Breaking Change Checker online! [{workflow_name}]({run_url}) is analyzing API compatibility on this {event_type}...\",\"runSuccess\":\"✅ Analysis complete! [{workflow_name}]({run_url}) has reviewed all changes. Compatibility verdict delivered! 📋\",\"runFailure\":\"🔬 Analysis interrupted! [{workflow_name}]({run_url}) {status}. Compatibility status unknown...\"}" + GH_AW_TRACKER_ID: "breaking-change-checker" + GH_AW_WORKFLOW_ID: "breaking-change-checker" + GH_AW_WORKFLOW_NAME: "Breaking Change Checker" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -7437,10 +7441,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[breaking-change] " GH_AW_ISSUE_LABELS: "breaking-change,automated-analysis" - GH_AW_WORKFLOW_NAME: "Breaking Change Checker" - GH_AW_TRACKER_ID: "breaking-change-checker" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e ⚠️ *Compatibility report by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔬 Breaking Change Checker online! [{workflow_name}]({run_url}) is analyzing API compatibility on this {event_type}...\",\"runSuccess\":\"✅ Analysis complete! [{workflow_name}]({run_url}) has reviewed all changes. Compatibility verdict delivered! 📋\",\"runFailure\":\"🔬 Analysis interrupted! [{workflow_name}]({run_url}) {status}. Compatibility status unknown...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/campaign-generator.lock.yml b/.github/workflows/campaign-generator.lock.yml index 956d85fa766..ff5b4bb6b40 100644 --- a/.github/workflows/campaign-generator.lock.yml +++ b/.github/workflows/campaign-generator.lock.yml @@ -6687,7 +6687,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "campaign-generator" + GH_AW_WORKFLOW_NAME: "Campaign Generator" outputs: assign_to_agent_assigned: ${{ steps.assign_to_agent.outputs.assigned }} steps: @@ -7780,8 +7782,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Campaign Generator" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_AGENT_TOKEN }} script: | @@ -7960,8 +7960,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Campaign Generator" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index 36f0c739aa5..f5758d31300 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -7452,7 +7452,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "codex" + GH_AW_ENGINE_MODEL: "gpt-5-mini" + GH_AW_WORKFLOW_ID: "changeset" + GH_AW_WORKFLOW_NAME: "Changeset Generator" outputs: push_to_pull_request_branch_commit_url: ${{ steps.push_to_pull_request_branch.outputs.commit_url }} steps: @@ -8693,9 +8696,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Changeset Generator" - GH_AW_ENGINE_ID: "codex" - GH_AW_ENGINE_MODEL: "gpt-5-mini" with: github-token: ${{ steps.app-token.outputs.token }} script: | @@ -8777,9 +8777,6 @@ jobs: GH_AW_PUSH_IF_NO_CHANGES: "warn" GH_AW_COMMIT_TITLE_SUFFIX: " [skip-ci]" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Changeset Generator" - GH_AW_ENGINE_ID: "codex" - GH_AW_ENGINE_MODEL: "gpt-5-mini" with: github-token: ${{ steps.app-token.outputs.token }} script: | diff --git a/.github/workflows/ci-coach.lock.yml b/.github/workflows/ci-coach.lock.yml index e88d5df1580..d46fe154c0d 100644 --- a/.github/workflows/ci-coach.lock.yml +++ b/.github/workflows/ci-coach.lock.yml @@ -7339,7 +7339,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "ci-coach-daily" + GH_AW_WORKFLOW_ID: "ci-coach" + GH_AW_WORKFLOW_NAME: "CI Optimization Coach" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7636,9 +7639,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "CI Optimization Coach" - GH_AW_TRACKER_ID: "ci-coach-daily" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index a49063de839..5051ff968d6 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -6850,7 +6850,12 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🩺 *Diagnosis provided by [{workflow_name}]({run_url})*\",\"runStarted\":\"🏥 CI Doctor reporting for duty! [{workflow_name}]({run_url}) is examining the patient on this {event_type}...\",\"runSuccess\":\"🩺 Examination complete! [{workflow_name}]({run_url}) has delivered the diagnosis. Prescription issued! 💊\",\"runFailure\":\"🏥 Medical emergency! [{workflow_name}]({run_url}) {status}. Doctor needs assistance...\"}" + GH_AW_WORKFLOW_ID: "ci-doctor" + GH_AW_WORKFLOW_NAME: "CI Failure Doctor" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/ci-doctor.md@ea350161ad5dcc9624cf510f134c6a9e39a6f94d" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/tree/ea350161ad5dcc9624cf510f134c6a9e39a6f94d/workflows/ci-doctor.md" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -7814,11 +7819,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[CI Failure Doctor] " - GH_AW_WORKFLOW_NAME: "CI Failure Doctor" - GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/ci-doctor.md@ea350161ad5dcc9624cf510f134c6a9e39a6f94d" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/tree/ea350161ad5dcc9624cf510f134c6a9e39a6f94d/workflows/ci-doctor.md" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🩺 *Diagnosis provided by [{workflow_name}]({run_url})*\",\"runStarted\":\"🏥 CI Doctor reporting for duty! [{workflow_name}]({run_url}) is examining the patient on this {event_type}...\",\"runSuccess\":\"🩺 Examination complete! [{workflow_name}]({run_url}) has delivered the diagnosis. Prescription issued! 💊\",\"runFailure\":\"🏥 Medical emergency! [{workflow_name}]({run_url}) {status}. Doctor needs assistance...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8120,11 +8120,6 @@ jobs: GH_AW_CREATED_ISSUE_URL: ${{ steps.create_issue.outputs.issue_url }} GH_AW_CREATED_ISSUE_NUMBER: ${{ steps.create_issue.outputs.issue_number }} GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }} - GH_AW_WORKFLOW_NAME: "CI Failure Doctor" - GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/ci-doctor.md@ea350161ad5dcc9624cf510f134c6a9e39a6f94d" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/tree/ea350161ad5dcc9624cf510f134c6a9e39a6f94d/workflows/ci-doctor.md" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🩺 *Diagnosis provided by [{workflow_name}]({run_url})*\",\"runStarted\":\"🏥 CI Doctor reporting for duty! [{workflow_name}]({run_url}) is examining the patient on this {event_type}...\",\"runSuccess\":\"🩺 Examination complete! [{workflow_name}]({run_url}) has delivered the diagnosis. Prescription issued! 💊\",\"runFailure\":\"🏥 Medical emergency! [{workflow_name}]({run_url}) {status}. Doctor needs assistance...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/cli-consistency-checker.lock.yml b/.github/workflows/cli-consistency-checker.lock.yml index 76525f632e6..507508c29c7 100644 --- a/.github/workflows/cli-consistency-checker.lock.yml +++ b/.github/workflows/cli-consistency-checker.lock.yml @@ -6566,7 +6566,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "cli-consistency-checker" + GH_AW_WORKFLOW_NAME: "CLI Consistency Checker" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -7229,8 +7231,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[cli-consistency] " GH_AW_ISSUE_LABELS: "automation,cli,documentation" - GH_AW_WORKFLOW_NAME: "CLI Consistency Checker" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index 37db31b7c00..6c0fe8d34b1 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -6411,7 +6411,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "cli-version-checker" + GH_AW_WORKFLOW_NAME: "CLI Version Checker" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -7074,8 +7076,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[ca] " GH_AW_ISSUE_LABELS: "automation,dependencies" - GH_AW_WORKFLOW_NAME: "CLI Version Checker" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index d6eb4b99692..6824cf96371 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -7516,7 +7516,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🎤 *Magnifique! Performance by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎵 Comme d'habitude! [{workflow_name}]({run_url}) takes the stage on this {event_type}...\",\"runSuccess\":\"🎤 Bravo! [{workflow_name}]({run_url}) has delivered a stunning performance! Standing ovation! 🌟\",\"runFailure\":\"🎵 Intermission... [{workflow_name}]({run_url}) {status}. The show must go on... eventually!\"}" + GH_AW_WORKFLOW_ID: "cloclo" + GH_AW_WORKFLOW_NAME: "/cloclo" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8395,9 +8398,6 @@ jobs: GH_AW_MAX_PATCH_SIZE: 1024 GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} - GH_AW_WORKFLOW_NAME: "/cloclo" - GH_AW_ENGINE_ID: "claude" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🎤 *Magnifique! Performance by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎵 Comme d'habitude! [{workflow_name}]({run_url}) takes the stage on this {event_type}...\",\"runSuccess\":\"🎤 Bravo! [{workflow_name}]({run_url}) has delivered a stunning performance! Standing ovation! 🌟\",\"runFailure\":\"🎵 Intermission... [{workflow_name}]({run_url}) {status}. The show must go on... eventually!\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8894,9 +8894,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_CREATED_PULL_REQUEST_URL: ${{ steps.create_pull_request.outputs.pull_request_url }} GH_AW_CREATED_PULL_REQUEST_NUMBER: ${{ steps.create_pull_request.outputs.pull_request_number }} - GH_AW_WORKFLOW_NAME: "/cloclo" - GH_AW_ENGINE_ID: "claude" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🎤 *Magnifique! Performance by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎵 Comme d'habitude! [{workflow_name}]({run_url}) takes the stage on this {event_type}...\",\"runSuccess\":\"🎤 Bravo! [{workflow_name}]({run_url}) has delivered a stunning performance! Standing ovation! 🌟\",\"runFailure\":\"🎵 Intermission... [{workflow_name}]({run_url}) {status}. The show must go on... eventually!\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/close-old-discussions.lock.yml b/.github/workflows/close-old-discussions.lock.yml index da1e5bf93e1..4d8136482a2 100644 --- a/.github/workflows/close-old-discussions.lock.yml +++ b/.github/workflows/close-old-discussions.lock.yml @@ -6501,7 +6501,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "codex" + GH_AW_WORKFLOW_ID: "close-old-discussions" + GH_AW_WORKFLOW_NAME: "Close Outdated Discussions" steps: - name: Download agent output artifact continue-on-error: true @@ -6770,8 +6772,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Close Outdated Discussions" - GH_AW_ENGINE_ID: "codex" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/commit-changes-analyzer.lock.yml b/.github/workflows/commit-changes-analyzer.lock.yml index 88858631efd..033bd8d5596 100644 --- a/.github/workflows/commit-changes-analyzer.lock.yml +++ b/.github/workflows/commit-changes-analyzer.lock.yml @@ -6324,7 +6324,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "commit-changes-analyzer" + GH_AW_WORKFLOW_NAME: "Commit Changes Analyzer" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7227,8 +7229,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Commit Changes Analyzer" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index e3183bda3a9..4439380aa93 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -6721,7 +6721,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "copilot-agent-analysis" + GH_AW_WORKFLOW_NAME: "Copilot Agent PR Analysis" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7624,8 +7626,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Copilot Agent PR Analysis" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/copilot-pr-merged-report.lock.yml b/.github/workflows/copilot-pr-merged-report.lock.yml index 094bd551573..6141aba9710 100644 --- a/.github/workflows/copilot-pr-merged-report.lock.yml +++ b/.github/workflows/copilot-pr-merged-report.lock.yml @@ -7951,7 +7951,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "copilot-pr-merged-report" + GH_AW_WORKFLOW_NAME: "Daily Copilot PR Merged Report" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8854,8 +8856,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Copilot PR Merged Report" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index 4b2a3e1e81b..db69641cd26 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -7368,7 +7368,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "copilot-pr-nlp-analysis" + GH_AW_WORKFLOW_NAME: "Copilot PR Conversation NLP Analysis" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8271,8 +8273,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Copilot PR Conversation NLP Analysis" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8557,8 +8557,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Copilot PR Conversation NLP Analysis" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 429b876cde4..f82971ab6ce 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -6853,7 +6853,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "copilot-pr-prompt-analysis" + GH_AW_WORKFLOW_NAME: "Copilot PR Prompt Pattern Analysis" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7756,8 +7758,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Copilot PR Prompt Pattern Analysis" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index f8b6114c57b..ad44e43bc23 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -7460,7 +7460,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "copilot-session-insights" + GH_AW_WORKFLOW_NAME: "Copilot Session Insights" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8363,8 +8365,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Copilot Session Insights" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8649,8 +8649,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Copilot Session Insights" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index 2d5f12a3bbb..cc4a6b66447 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -7626,7 +7626,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e ⚒️ *Crafted with care by [{workflow_name}]({run_url})*\",\"runStarted\":\"🛠️ Master Crafter at work! [{workflow_name}]({run_url}) is forging a new workflow on this {event_type}...\",\"runSuccess\":\"⚒️ Masterpiece complete! [{workflow_name}]({run_url}) has crafted your workflow. May it serve you well! 🎖️\",\"runFailure\":\"🛠️ Forge cooling down! [{workflow_name}]({run_url}) {status}. The anvil awaits another attempt...\"}" + GH_AW_WORKFLOW_ID: "craft" + GH_AW_WORKFLOW_NAME: "Workflow Craft Agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8450,9 +8453,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Workflow Craft Agent" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e ⚒️ *Crafted with care by [{workflow_name}]({run_url})*\",\"runStarted\":\"🛠️ Master Crafter at work! [{workflow_name}]({run_url}) is forging a new workflow on this {event_type}...\",\"runSuccess\":\"⚒️ Masterpiece complete! [{workflow_name}]({run_url}) has crafted your workflow. May it serve you well! 🎖️\",\"runFailure\":\"🛠️ Forge cooling down! [{workflow_name}]({run_url}) {status}. The anvil awaits another attempt...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8862,9 +8862,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_PUSH_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Workflow Craft Agent" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e ⚒️ *Crafted with care by [{workflow_name}]({run_url})*\",\"runStarted\":\"🛠️ Master Crafter at work! [{workflow_name}]({run_url}) is forging a new workflow on this {event_type}...\",\"runSuccess\":\"⚒️ Masterpiece complete! [{workflow_name}]({run_url}) has crafted your workflow. May it serve you well! 🎖️\",\"runFailure\":\"🛠️ Forge cooling down! [{workflow_name}]({run_url}) {status}. The anvil awaits another attempt...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-assign-issue-to-user.lock.yml b/.github/workflows/daily-assign-issue-to-user.lock.yml index 5c7ffbaa170..7ffa8644327 100644 --- a/.github/workflows/daily-assign-issue-to-user.lock.yml +++ b/.github/workflows/daily-assign-issue-to-user.lock.yml @@ -6361,7 +6361,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "daily-assign-issue-to-user" + GH_AW_WORKFLOW_NAME: "Auto-Assign Issue" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -7659,8 +7661,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_COMMENT_TARGET: "*" - GH_AW_WORKFLOW_NAME: "Auto-Assign Issue" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8068,8 +8068,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Auto-Assign Issue" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-code-metrics.lock.yml b/.github/workflows/daily-code-metrics.lock.yml index a86091e8821..3213df2c5df 100644 --- a/.github/workflows/daily-code-metrics.lock.yml +++ b/.github/workflows/daily-code-metrics.lock.yml @@ -6958,7 +6958,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_TRACKER_ID: "daily-code-metrics" + GH_AW_WORKFLOW_ID: "daily-code-metrics" + GH_AW_WORKFLOW_NAME: "Daily Code Metrics and Trend Tracking Agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7861,9 +7864,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Code Metrics and Trend Tracking Agent" - GH_AW_TRACKER_ID: "daily-code-metrics" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-copilot-token-report.lock.yml b/.github/workflows/daily-copilot-token-report.lock.yml index d8fe1145535..277b0d14917 100644 --- a/.github/workflows/daily-copilot-token-report.lock.yml +++ b/.github/workflows/daily-copilot-token-report.lock.yml @@ -7457,7 +7457,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "daily-copilot-token-report" + GH_AW_WORKFLOW_ID: "daily-copilot-token-report" + GH_AW_WORKFLOW_NAME: "Daily Copilot Token Consumption Report" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8360,9 +8363,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Copilot Token Consumption Report" - GH_AW_TRACKER_ID: "daily-copilot-token-report" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8647,9 +8647,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Copilot Token Consumption Report" - GH_AW_TRACKER_ID: "daily-copilot-token-report" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index 772ca031de6..00f51e42caf 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -6260,7 +6260,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_TRACKER_ID: "daily-doc-updater" + GH_AW_WORKFLOW_ID: "daily-doc-updater" + GH_AW_WORKFLOW_NAME: "Daily Documentation Updater" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6558,9 +6561,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Daily Documentation Updater" - GH_AW_TRACKER_ID: "daily-doc-updater" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml index ae1d9a85c1d..c5f054d9c56 100644 --- a/.github/workflows/daily-fact.lock.yml +++ b/.github/workflows/daily-fact.lock.yml @@ -6321,7 +6321,12 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "codex" + GH_AW_ENGINE_MODEL: "gpt-5-mini" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Penned with care by [{workflow_name}]({run_url})*\",\"runStarted\":\"📜 Hark! The muse awakens — [{workflow_name}]({run_url}) begins its verse upon this {event_type}...\",\"runSuccess\":\"✨ Lo! [{workflow_name}]({run_url}) hath woven its tale to completion, like a sonnet finding its final rhyme. 🌟\",\"runFailure\":\"🌧️ Alas! [{workflow_name}]({run_url}) {status}, its quill fallen mid-verse. The poem remains unfinished...\"}" + GH_AW_TRACKER_ID: "daily-fact-thread" + GH_AW_WORKFLOW_ID: "daily-fact" + GH_AW_WORKFLOW_NAME: "Daily Fact About gh-aw" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -6950,11 +6955,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_COMMENT_TARGET: "4750" GITHUB_AW_COMMENT_DISCUSSION: "true" - GH_AW_WORKFLOW_NAME: "Daily Fact About gh-aw" - GH_AW_TRACKER_ID: "daily-fact-thread" - GH_AW_ENGINE_ID: "codex" - GH_AW_ENGINE_MODEL: "gpt-5-mini" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Penned with care by [{workflow_name}]({run_url})*\",\"runStarted\":\"📜 Hark! The muse awakens — [{workflow_name}]({run_url}) begins its verse upon this {event_type}...\",\"runSuccess\":\"✨ Lo! [{workflow_name}]({run_url}) hath woven its tale to completion, like a sonnet finding its final rhyme. 🌟\",\"runFailure\":\"🌧️ Alas! [{workflow_name}]({run_url}) {status}, its quill fallen mid-verse. The poem remains unfinished...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-file-diet.lock.yml b/.github/workflows/daily-file-diet.lock.yml index b13fb009e18..fcee83d2e14 100644 --- a/.github/workflows/daily-file-diet.lock.yml +++ b/.github/workflows/daily-file-diet.lock.yml @@ -7838,7 +7838,10 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "daily-file-diet" + GH_AW_WORKFLOW_ID: "daily-file-diet" + GH_AW_WORKFLOW_NAME: "Daily File Diet" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -8512,9 +8515,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[file-diet] " GH_AW_ISSUE_LABELS: "refactoring,code-health,automated-analysis" - GH_AW_WORKFLOW_NAME: "Daily File Diet" - GH_AW_TRACKER_ID: "daily-file-diet" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ steps.app-token.outputs.token }} script: | @@ -8813,9 +8813,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily File Diet" - GH_AW_TRACKER_ID: "daily-file-diet" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ steps.app-token.outputs.token }} script: | diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml index d7ee89e55f9..7b12d04bdca 100644 --- a/.github/workflows/daily-firewall-report.lock.yml +++ b/.github/workflows/daily-firewall-report.lock.yml @@ -7340,7 +7340,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "daily-firewall-report" + GH_AW_WORKFLOW_ID: "daily-firewall-report" + GH_AW_WORKFLOW_NAME: "Daily Firewall Logs Collector and Reporter" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8243,9 +8246,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Firewall Logs Collector and Reporter" - GH_AW_TRACKER_ID: "daily-firewall-report" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8530,9 +8530,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Firewall Logs Collector and Reporter" - GH_AW_TRACKER_ID: "daily-firewall-report" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-issues-report.lock.yml b/.github/workflows/daily-issues-report.lock.yml index 66e81da1668..f9831811c86 100644 --- a/.github/workflows/daily-issues-report.lock.yml +++ b/.github/workflows/daily-issues-report.lock.yml @@ -7498,7 +7498,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "codex" + GH_AW_TRACKER_ID: "daily-issues-report" + GH_AW_WORKFLOW_ID: "daily-issues-report" + GH_AW_WORKFLOW_NAME: "Daily Issues Report Generator" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8530,9 +8533,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Issues Report Generator" - GH_AW_TRACKER_ID: "daily-issues-report" - GH_AW_ENGINE_ID: "codex" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8817,9 +8817,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Issues Report Generator" - GH_AW_TRACKER_ID: "daily-issues-report" - GH_AW_ENGINE_ID: "codex" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9056,9 +9053,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Issues Report Generator" - GH_AW_TRACKER_ID: "daily-issues-report" - GH_AW_ENGINE_ID: "codex" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-malicious-code-scan.lock.yml b/.github/workflows/daily-malicious-code-scan.lock.yml index f851cd4e5c4..60afd382c58 100644 --- a/.github/workflows/daily-malicious-code-scan.lock.yml +++ b/.github/workflows/daily-malicious-code-scan.lock.yml @@ -6429,7 +6429,10 @@ jobs: security-events: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "malicious-code-scan" + GH_AW_WORKFLOW_ID: "daily-malicious-code-scan" + GH_AW_WORKFLOW_NAME: "Daily Malicious Code Scan Agent" steps: - name: Download agent output artifact continue-on-error: true @@ -6547,9 +6550,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_FILENAME: "daily-malicious-code-scan" - GH_AW_WORKFLOW_NAME: "Daily Malicious Code Scan Agent" - GH_AW_TRACKER_ID: "malicious-code-scan" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index a6f0ce2812c..d0a6dc8912a 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -6234,7 +6234,10 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_TRACKER_ID: "daily-multi-device-docs-tester" + GH_AW_WORKFLOW_ID: "daily-multi-device-docs-tester" + GH_AW_WORKFLOW_NAME: "Multi-Device Docs Tester" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -6895,9 +6898,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Multi-Device Docs Tester" - GH_AW_TRACKER_ID: "daily-multi-device-docs-tester" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -7196,9 +7196,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Multi-Device Docs Tester" - GH_AW_TRACKER_ID: "daily-multi-device-docs-tester" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index f0170d05f81..5c861a2134d 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -7262,7 +7262,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "daily-news-weekday" + GH_AW_WORKFLOW_ID: "daily-news" + GH_AW_WORKFLOW_NAME: "Daily News" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8165,9 +8168,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily News" - GH_AW_TRACKER_ID: "daily-news-weekday" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8452,9 +8452,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily News" - GH_AW_TRACKER_ID: "daily-news-weekday" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-performance-summary.lock.yml b/.github/workflows/daily-performance-summary.lock.yml index 0cbd6b3c20d..acd1a2b3ee0 100644 --- a/.github/workflows/daily-performance-summary.lock.yml +++ b/.github/workflows/daily-performance-summary.lock.yml @@ -8961,7 +8961,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "codex" + GH_AW_TRACKER_ID: "daily-performance-summary" + GH_AW_WORKFLOW_ID: "daily-performance-summary" + GH_AW_WORKFLOW_NAME: "Daily Project Performance Summary Generator (Using Safe Inputs)" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -9993,9 +9996,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Project Performance Summary Generator (Using Safe Inputs)" - GH_AW_TRACKER_ID: "daily-performance-summary" - GH_AW_ENGINE_ID: "codex" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -10280,9 +10280,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Project Performance Summary Generator (Using Safe Inputs)" - GH_AW_TRACKER_ID: "daily-performance-summary" - GH_AW_ENGINE_ID: "codex" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -10519,9 +10516,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Project Performance Summary Generator (Using Safe Inputs)" - GH_AW_TRACKER_ID: "daily-performance-summary" - GH_AW_ENGINE_ID: "codex" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-repo-chronicle.lock.yml b/.github/workflows/daily-repo-chronicle.lock.yml index 9534a2987aa..2a15fe6d564 100644 --- a/.github/workflows/daily-repo-chronicle.lock.yml +++ b/.github/workflows/daily-repo-chronicle.lock.yml @@ -7183,7 +7183,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "daily-repo-chronicle" + GH_AW_WORKFLOW_ID: "daily-repo-chronicle" + GH_AW_WORKFLOW_NAME: "The Daily Repository Chronicle" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8086,9 +8089,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "The Daily Repository Chronicle" - GH_AW_TRACKER_ID: "daily-repo-chronicle" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8373,9 +8373,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "The Daily Repository Chronicle" - GH_AW_TRACKER_ID: "daily-repo-chronicle" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-team-status.lock.yml b/.github/workflows/daily-team-status.lock.yml index dd9541daf71..ca804598c4b 100644 --- a/.github/workflows/daily-team-status.lock.yml +++ b/.github/workflows/daily-team-status.lock.yml @@ -6471,7 +6471,12 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "daily-team-status" + GH_AW_WORKFLOW_ID: "daily-team-status" + GH_AW_WORKFLOW_NAME: "Daily Team Status" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-team-status.md@d3422bf940923ef1d43db5559652b8e1e71869f3" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/tree/d3422bf940923ef1d43db5559652b8e1e71869f3/workflows/daily-team-status.md" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7374,11 +7379,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Team Status" - GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-team-status.md@d3422bf940923ef1d43db5559652b8e1e71869f3" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/tree/d3422bf940923ef1d43db5559652b8e1e71869f3/workflows/daily-team-status.md" - GH_AW_TRACKER_ID: "daily-team-status" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-workflow-updater.lock.yml b/.github/workflows/daily-workflow-updater.lock.yml index 3380b8694d3..bb4f882768f 100644 --- a/.github/workflows/daily-workflow-updater.lock.yml +++ b/.github/workflows/daily-workflow-updater.lock.yml @@ -6530,7 +6530,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "daily-workflow-updater" + GH_AW_WORKFLOW_ID: "daily-workflow-updater" + GH_AW_WORKFLOW_NAME: "Daily Workflow Updater" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6828,9 +6831,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Daily Workflow Updater" - GH_AW_TRACKER_ID: "daily-workflow-updater" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index 886370667d8..a78903d7854 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -7182,7 +7182,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "codex" + GH_AW_TRACKER_ID: "deep-report-intel-agent" + GH_AW_WORKFLOW_ID: "deep-report" + GH_AW_WORKFLOW_NAME: "DeepReport - Intelligence Gathering Agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8085,9 +8088,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "DeepReport - Intelligence Gathering Agent" - GH_AW_TRACKER_ID: "deep-report-intel-agent" - GH_AW_ENGINE_ID: "codex" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8372,9 +8372,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "DeepReport - Intelligence Gathering Agent" - GH_AW_TRACKER_ID: "deep-report-intel-agent" - GH_AW_ENGINE_ID: "codex" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/dependabot-go-checker.lock.yml b/.github/workflows/dependabot-go-checker.lock.yml index fb5f682b3a7..51a51a170ee 100644 --- a/.github/workflows/dependabot-go-checker.lock.yml +++ b/.github/workflows/dependabot-go-checker.lock.yml @@ -6835,7 +6835,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "dependabot-go-checker" + GH_AW_WORKFLOW_NAME: "Dependabot Dependency Checker" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -7928,8 +7930,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[deps]" GH_AW_ISSUE_LABELS: "dependencies,go" - GH_AW_WORKFLOW_NAME: "Dependabot Dependency Checker" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8228,8 +8228,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Dependabot Dependency Checker" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index 739aedeaf50..f214ea1393e 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -6660,7 +6660,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🦅 *Observed from above by [{workflow_name}]({run_url})*\",\"runStarted\":\"🦅 Dev Hawk circles the sky! [{workflow_name}]({run_url}) is monitoring this {event_type} from above...\",\"runSuccess\":\"🦅 Hawk eyes report! [{workflow_name}]({run_url}) has completed reconnaissance. Intel delivered! 🎯\",\"runFailure\":\"🦅 Hawk down! [{workflow_name}]({run_url}) {status}. The skies grow quiet...\"}" + GH_AW_WORKFLOW_ID: "dev-hawk" + GH_AW_WORKFLOW_NAME: "Dev Hawk" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -7288,9 +7291,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_COMMENT_TARGET: "*" - GH_AW_WORKFLOW_NAME: "Dev Hawk" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🦅 *Observed from above by [{workflow_name}]({run_url})*\",\"runStarted\":\"🦅 Dev Hawk circles the sky! [{workflow_name}]({run_url}) is monitoring this {event_type} from above...\",\"runSuccess\":\"🦅 Hawk eyes report! [{workflow_name}]({run_url}) has completed reconnaissance. Intel delivered! 🎯\",\"runFailure\":\"🦅 Hawk down! [{workflow_name}]({run_url}) {status}. The skies grow quiet...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index c8e95f491e5..2f9e7f95d2b 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -7722,7 +7722,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📝 Poetry generated by [{workflow_name}]({run_url})\",\"footerInstall\":\"\\u003e Want to add poems to your discussions? Install with `gh aw add {workflow_source}`\"}" + GH_AW_WORKFLOW_ID: "dev" + GH_AW_WORKFLOW_NAME: "Dev" outputs: update_discussion_discussion_number: ${{ steps.update_discussion.outputs.discussion_number }} update_discussion_discussion_url: ${{ steps.update_discussion.outputs.discussion_url }} @@ -8662,9 +8665,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Dev" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📝 Poetry generated by [{workflow_name}]({run_url})\",\"footerInstall\":\"\\u003e Want to add poems to your discussions? Install with `gh aw add {workflow_source}`\"}" GH_AW_UPDATE_TARGET: "*" GH_AW_UPDATE_BODY: "true" with: diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index 2094a49f487..52ad48a76cc 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -6877,7 +6877,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "developer-docs-consolidator" + GH_AW_WORKFLOW_NAME: "Developer Documentation Consolidator" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7940,8 +7942,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Developer Documentation Consolidator" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8233,8 +8233,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Developer Documentation Consolidator" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index e673f756ec8..07a38c08213 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -6492,7 +6492,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "dictation-prompt" + GH_AW_WORKFLOW_NAME: "Dictation Prompt Generator" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6790,8 +6792,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Dictation Prompt Generator" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/docs-noob-tester.lock.yml b/.github/workflows/docs-noob-tester.lock.yml index 79e4d65afa3..a5c1a666e43 100644 --- a/.github/workflows/docs-noob-tester.lock.yml +++ b/.github/workflows/docs-noob-tester.lock.yml @@ -6585,7 +6585,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "docs-noob-tester" + GH_AW_WORKFLOW_NAME: "Documentation Noob Tester" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7488,8 +7490,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Documentation Noob Tester" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -7774,8 +7774,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Documentation Noob Tester" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index a1d609a89a8..cdce8a788f0 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -6549,7 +6549,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "codex" + GH_AW_WORKFLOW_ID: "duplicate-code-detector" + GH_AW_WORKFLOW_NAME: "Duplicate Code Detector" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -7212,8 +7214,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[duplicate-code] " GH_AW_ISSUE_LABELS: "code-quality,automated-analysis" - GH_AW_WORKFLOW_NAME: "Duplicate Code Detector" - GH_AW_ENGINE_ID: "codex" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/example-workflow-analyzer.lock.yml b/.github/workflows/example-workflow-analyzer.lock.yml index 9892a54f99b..cfad1813918 100644 --- a/.github/workflows/example-workflow-analyzer.lock.yml +++ b/.github/workflows/example-workflow-analyzer.lock.yml @@ -6072,7 +6072,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "example-workflow-analyzer" + GH_AW_WORKFLOW_NAME: "Weekly Workflow Analysis" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -6975,8 +6977,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Weekly Workflow Analysis" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/github-mcp-structural-analysis.lock.yml b/.github/workflows/github-mcp-structural-analysis.lock.yml index 830c26aaa41..d990085ae4d 100644 --- a/.github/workflows/github-mcp-structural-analysis.lock.yml +++ b/.github/workflows/github-mcp-structural-analysis.lock.yml @@ -6813,7 +6813,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "github-mcp-structural-analysis" + GH_AW_WORKFLOW_NAME: "GitHub MCP Structural Analysis" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7716,8 +7718,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "GitHub MCP Structural Analysis" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8002,8 +8002,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "GitHub MCP Structural Analysis" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index fefed39a364..f380a837d51 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -6688,7 +6688,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "github-mcp-tools-report" + GH_AW_WORKFLOW_NAME: "GitHub MCP Remote Server Tools Report Generator" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7751,8 +7753,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "GitHub MCP Remote Server Tools Report Generator" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8044,8 +8044,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "GitHub MCP Remote Server Tools Report Generator" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index 5ac8f83296d..4d47cf86e7b 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -7119,7 +7119,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "glossary-maintainer" + GH_AW_WORKFLOW_NAME: "Glossary Maintainer" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7417,8 +7419,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Glossary Maintainer" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/go-fan.lock.yml b/.github/workflows/go-fan.lock.yml index e13eaf67487..56b8c5d6e09 100644 --- a/.github/workflows/go-fan.lock.yml +++ b/.github/workflows/go-fan.lock.yml @@ -6468,7 +6468,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_TRACKER_ID: "go-fan-daily" + GH_AW_WORKFLOW_ID: "go-fan" + GH_AW_WORKFLOW_NAME: "Go Fan" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7371,9 +7374,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Go Fan" - GH_AW_TRACKER_ID: "go-fan-daily" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/go-file-size-reduction-project64.campaign.g.lock.yml b/.github/workflows/go-file-size-reduction-project64.campaign.g.lock.yml index 524a1b7ecd5..4f27baaf386 100644 --- a/.github/workflows/go-file-size-reduction-project64.campaign.g.lock.yml +++ b/.github/workflows/go-file-size-reduction-project64.campaign.g.lock.yml @@ -6557,7 +6557,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "go-file-size-reduction-project64.campaign.g" + GH_AW_WORKFLOW_NAME: "Go File Size Reduction Campaign (Project 64)" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -7184,8 +7186,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Go File Size Reduction Campaign (Project 64)" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -7593,8 +7593,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Go File Size Reduction Campaign (Project 64)" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }} script: | diff --git a/.github/workflows/go-file-size-reduction.campaign.g.lock.yml b/.github/workflows/go-file-size-reduction.campaign.g.lock.yml index 5a98a7678cf..7e77bf580a7 100644 --- a/.github/workflows/go-file-size-reduction.campaign.g.lock.yml +++ b/.github/workflows/go-file-size-reduction.campaign.g.lock.yml @@ -6557,7 +6557,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "go-file-size-reduction.campaign.g" + GH_AW_WORKFLOW_NAME: "Go File Size Reduction Campaign" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -7184,8 +7186,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Go File Size Reduction Campaign" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -7593,8 +7593,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Go File Size Reduction Campaign" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }} script: | diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index 6d4379e43db..d0a9bbf62a2 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -6335,7 +6335,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "go-logger" + GH_AW_WORKFLOW_NAME: "Go Logger Enhancement" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6633,8 +6635,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Go Logger Enhancement" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/go-pattern-detector.lock.yml b/.github/workflows/go-pattern-detector.lock.yml index e250b883305..7dacd3fad2e 100644 --- a/.github/workflows/go-pattern-detector.lock.yml +++ b/.github/workflows/go-pattern-detector.lock.yml @@ -6199,7 +6199,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "go-pattern-detector" + GH_AW_WORKFLOW_NAME: "Go Pattern Detector" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -6862,8 +6864,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[ast-grep] " GH_AW_ISSUE_LABELS: "code-quality,ast-grep" - GH_AW_WORKFLOW_NAME: "Go Pattern Detector" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml index abaade10527..5caa79a1264 100644 --- a/.github/workflows/grumpy-reviewer.lock.yml +++ b/.github/workflows/grumpy-reviewer.lock.yml @@ -7557,7 +7557,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 😤 *Reluctantly reviewed by [{workflow_name}]({run_url})*\",\"runStarted\":\"😤 *sigh* [{workflow_name}]({run_url}) is begrudgingly looking at this {event_type}... This better be worth my time.\",\"runSuccess\":\"😤 Fine. [{workflow_name}]({run_url}) finished the review. It wasn't completely terrible. I guess. 🙄\",\"runFailure\":\"😤 Great. [{workflow_name}]({run_url}) {status}. As if my day couldn't get any worse...\"}" + GH_AW_WORKFLOW_ID: "grumpy-reviewer" + GH_AW_WORKFLOW_NAME: "Grumpy Code Reviewer 🔥" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8319,9 +8322,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Grumpy Code Reviewer 🔥" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 😤 *Reluctantly reviewed by [{workflow_name}]({run_url})*\",\"runStarted\":\"😤 *sigh* [{workflow_name}]({run_url}) is begrudgingly looking at this {event_type}... This better be worth my time.\",\"runSuccess\":\"😤 Fine. [{workflow_name}]({run_url}) finished the review. It wasn't completely terrible. I guess. 🙄\",\"runFailure\":\"😤 Great. [{workflow_name}]({run_url}) {status}. As if my day couldn't get any worse...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8730,9 +8730,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_PR_REVIEW_COMMENT_SIDE: "RIGHT" - GH_AW_WORKFLOW_NAME: "Grumpy Code Reviewer 🔥" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 😤 *Reluctantly reviewed by [{workflow_name}]({run_url})*\",\"runStarted\":\"😤 *sigh* [{workflow_name}]({run_url}) is begrudgingly looking at this {event_type}... This better be worth my time.\",\"runSuccess\":\"😤 Fine. [{workflow_name}]({run_url}) finished the review. It wasn't completely terrible. I guess. 🙄\",\"runFailure\":\"😤 Great. [{workflow_name}]({run_url}) {status}. As if my day couldn't get any worse...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/hourly-ci-cleaner.lock.yml b/.github/workflows/hourly-ci-cleaner.lock.yml index fe5937a6117..0e6bd920531 100644 --- a/.github/workflows/hourly-ci-cleaner.lock.yml +++ b/.github/workflows/hourly-ci-cleaner.lock.yml @@ -6756,7 +6756,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "hourly-ci-cleaner" + GH_AW_WORKFLOW_ID: "hourly-ci-cleaner" + GH_AW_WORKFLOW_NAME: "Hourly CI Cleaner" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7053,9 +7056,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Hourly CI Cleaner" - GH_AW_TRACKER_ID: "hourly-ci-cleaner" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/human-ai-collaboration.lock.yml b/.github/workflows/human-ai-collaboration.lock.yml index a7affd49dde..b976e9b9dfd 100644 --- a/.github/workflows/human-ai-collaboration.lock.yml +++ b/.github/workflows/human-ai-collaboration.lock.yml @@ -7080,7 +7080,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "human-ai-collaboration" + GH_AW_WORKFLOW_NAME: "Human-AI Collaboration Campaign" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -7741,8 +7743,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Human-AI Collaboration Campaign" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/incident-response.lock.yml b/.github/workflows/incident-response.lock.yml index 8cfb834352b..544576cb753 100644 --- a/.github/workflows/incident-response.lock.yml +++ b/.github/workflows/incident-response.lock.yml @@ -7241,7 +7241,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "incident-response" + GH_AW_WORKFLOW_NAME: "Campaign - Incident Response" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8965,8 +8967,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_LABELS: "campaign-tracker,incident" - GH_AW_WORKFLOW_NAME: "Campaign - Incident Response" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9271,8 +9271,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Campaign - Incident Response" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9772,8 +9770,6 @@ jobs: GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }} GH_AW_CREATED_PULL_REQUEST_URL: ${{ steps.create_pull_request.outputs.pull_request_url }} GH_AW_CREATED_PULL_REQUEST_NUMBER: ${{ steps.create_pull_request.outputs.pull_request_number }} - GH_AW_WORKFLOW_NAME: "Campaign - Incident Response" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -10181,8 +10177,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Campaign - Incident Response" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index 5dd3357f5b8..738e6d32b7b 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -6215,7 +6215,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "instructions-janitor" + GH_AW_WORKFLOW_NAME: "Instructions Janitor" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6513,8 +6515,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Instructions Janitor" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/intelligence.lock.yml b/.github/workflows/intelligence.lock.yml index 829a030c5ad..ef0c7fc422b 100644 --- a/.github/workflows/intelligence.lock.yml +++ b/.github/workflows/intelligence.lock.yml @@ -7776,7 +7776,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "intelligence" + GH_AW_WORKFLOW_NAME: "Campaign Intelligence System" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -8437,8 +8439,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Campaign Intelligence System" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8737,8 +8737,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Campaign Intelligence System" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/issue-arborist.lock.yml b/.github/workflows/issue-arborist.lock.yml index 7ee87276ecd..743f95f2ccd 100644 --- a/.github/workflows/issue-arborist.lock.yml +++ b/.github/workflows/issue-arborist.lock.yml @@ -6620,7 +6620,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "codex" + GH_AW_WORKFLOW_ID: "issue-arborist" + GH_AW_WORKFLOW_NAME: "Issue Arborist" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7694,8 +7696,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[Parent] " - GH_AW_WORKFLOW_NAME: "Issue Arborist" - GH_AW_ENGINE_ID: "codex" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -7994,8 +7994,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Issue Arborist" - GH_AW_ENGINE_ID: "codex" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8282,8 +8280,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_CREATED_ISSUE_NUMBER: ${{ steps.create_issue.outputs.issue_number }} GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }} - GH_AW_WORKFLOW_NAME: "Issue Arborist" - GH_AW_ENGINE_ID: "codex" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index 23862aa51f8..d95c649eb4f 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -5266,7 +5266,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "custom" + GH_AW_WORKFLOW_ID: "issue-classifier" + GH_AW_WORKFLOW_NAME: "Issue Classifier" outputs: add_labels_labels_added: ${{ steps.add_labels.outputs.labels_added }} steps: @@ -6056,8 +6058,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_LABELS_ALLOWED: "bug,feature,enhancement,documentation" GH_AW_LABELS_MAX_COUNT: 1 - GH_AW_WORKFLOW_NAME: "Issue Classifier" - GH_AW_ENGINE_ID: "custom" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index 195883a3ca0..f897bc7de5f 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -6774,7 +6774,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🍪 *Om nom nom by [{workflow_name}]({run_url})*\",\"runStarted\":\"🍪 ISSUE! ISSUE! [{workflow_name}]({run_url}) hungry for issues on this {event_type}! Om nom nom...\",\"runSuccess\":\"🍪 YUMMY! [{workflow_name}]({run_url}) ate the issues! That was DELICIOUS! Me want MORE! 😋\",\"runFailure\":\"🍪 Aww... [{workflow_name}]({run_url}) {status}. No cookie for monster today... 😢\"}" + GH_AW_WORKFLOW_ID: "issue-monster" + GH_AW_WORKFLOW_NAME: "Issue Monster" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -7862,9 +7865,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Issue Monster" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🍪 *Om nom nom by [{workflow_name}]({run_url})*\",\"runStarted\":\"🍪 ISSUE! ISSUE! [{workflow_name}]({run_url}) hungry for issues on this {event_type}! Om nom nom...\",\"runSuccess\":\"🍪 YUMMY! [{workflow_name}]({run_url}) ate the issues! That was DELICIOUS! Me want MORE! 😋\",\"runFailure\":\"🍪 Aww... [{workflow_name}]({run_url}) {status}. No cookie for monster today... 😢\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8272,9 +8272,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Issue Monster" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🍪 *Om nom nom by [{workflow_name}]({run_url})*\",\"runStarted\":\"🍪 ISSUE! ISSUE! [{workflow_name}]({run_url}) hungry for issues on this {event_type}! Om nom nom...\",\"runSuccess\":\"🍪 YUMMY! [{workflow_name}]({run_url}) ate the issues! That was DELICIOUS! Me want MORE! 😋\",\"runFailure\":\"🍪 Aww... [{workflow_name}]({run_url}) {status}. No cookie for monster today... 😢\"}" with: github-token: ${{ secrets.GH_AW_AGENT_TOKEN }} script: | diff --git a/.github/workflows/issue-triage-agent.lock.yml b/.github/workflows/issue-triage-agent.lock.yml index d69db29fb4a..b707e5059ff 100644 --- a/.github/workflows/issue-triage-agent.lock.yml +++ b/.github/workflows/issue-triage-agent.lock.yml @@ -6336,7 +6336,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "issue-triage-agent" + GH_AW_WORKFLOW_NAME: "Issue Triage Agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -7633,8 +7635,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Issue Triage Agent" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8043,8 +8043,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_LABELS_ALLOWED: "bug,feature,enhancement,documentation,question,help-wanted,good-first-issue" - GH_AW_WORKFLOW_NAME: "Issue Triage Agent" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/jsweep.lock.yml b/.github/workflows/jsweep.lock.yml index 1f5f5ff84c0..a9a50035846 100644 --- a/.github/workflows/jsweep.lock.yml +++ b/.github/workflows/jsweep.lock.yml @@ -6628,7 +6628,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "jsweep-daily" + GH_AW_WORKFLOW_ID: "jsweep" + GH_AW_WORKFLOW_NAME: "jsweep - JavaScript Unbloater" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6926,9 +6929,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "ignore" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "jsweep - JavaScript Unbloater" - GH_AW_TRACKER_ID: "jsweep-daily" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/layout-spec-maintainer.lock.yml b/.github/workflows/layout-spec-maintainer.lock.yml index 915e16272ce..330cf70d138 100644 --- a/.github/workflows/layout-spec-maintainer.lock.yml +++ b/.github/workflows/layout-spec-maintainer.lock.yml @@ -6648,7 +6648,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "layout-spec-maintainer" + GH_AW_WORKFLOW_ID: "layout-spec-maintainer" + GH_AW_WORKFLOW_NAME: "Layout Specification Maintainer" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6946,9 +6949,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Layout Specification Maintainer" - GH_AW_TRACKER_ID: "layout-spec-maintainer" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index 1138d7692cc..2cfbf78a8b2 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -6459,7 +6459,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "lockfile-stats" + GH_AW_WORKFLOW_NAME: "Lockfile Statistics Analysis Agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7362,8 +7364,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Lockfile Statistics Analysis Agent" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index 7b2d1960044..50d810920e5 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -7285,7 +7285,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "mcp-inspector" + GH_AW_WORKFLOW_NAME: "MCP Inspector Agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8188,8 +8190,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "MCP Inspector Agent" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index f723f33a4ec..370493472f7 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -7339,7 +7339,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "mergefest" + GH_AW_WORKFLOW_NAME: "Mergefest" outputs: push_to_pull_request_branch_commit_url: ${{ steps.push_to_pull_request_branch.outputs.commit_url }} steps: @@ -7563,8 +7565,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_PUSH_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Mergefest" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/org-health-report.lock.yml b/.github/workflows/org-health-report.lock.yml index 68257ba63ad..17977f42100 100644 --- a/.github/workflows/org-health-report.lock.yml +++ b/.github/workflows/org-health-report.lock.yml @@ -7294,7 +7294,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "org-health-report" + GH_AW_WORKFLOW_NAME: "Organization Health Report" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8197,8 +8199,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Organization Health Report" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8483,8 +8483,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Organization Health Report" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/org-wide-rollout.lock.yml b/.github/workflows/org-wide-rollout.lock.yml index 8ad25564cd5..fef9c7098d0 100644 --- a/.github/workflows/org-wide-rollout.lock.yml +++ b/.github/workflows/org-wide-rollout.lock.yml @@ -7269,7 +7269,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "org-wide-rollout" + GH_AW_WORKFLOW_NAME: "Campaign - Org-Wide Rollout" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8993,8 +8995,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_LABELS: "campaign-tracker,org-rollout" - GH_AW_WORKFLOW_NAME: "Campaign - Org-Wide Rollout" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9299,8 +9299,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Campaign - Org-Wide Rollout" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9800,8 +9798,6 @@ jobs: GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }} GH_AW_CREATED_PULL_REQUEST_URL: ${{ steps.create_pull_request.outputs.pull_request_url }} GH_AW_CREATED_PULL_REQUEST_NUMBER: ${{ steps.create_pull_request.outputs.pull_request_number }} - GH_AW_WORKFLOW_NAME: "Campaign - Org-Wide Rollout" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -10209,8 +10205,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Campaign - Org-Wide Rollout" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index f6fe3867587..ddbab17634b 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -7549,7 +7549,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📄 *Summary compiled by [{workflow_name}]({run_url})*\",\"runStarted\":\"📖 Page by page! [{workflow_name}]({run_url}) is reading through this {event_type}...\",\"runSuccess\":\"📚 TL;DR ready! [{workflow_name}]({run_url}) has distilled the essence. Knowledge condensed! ✨\",\"runFailure\":\"📖 Reading interrupted! [{workflow_name}]({run_url}) {status}. The document remains unsummarized...\"}" + GH_AW_WORKFLOW_ID: "pdf-summary" + GH_AW_WORKFLOW_NAME: "Resource Summarizer Agent" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8176,9 +8179,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Resource Summarizer Agent" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📄 *Summary compiled by [{workflow_name}]({run_url})*\",\"runStarted\":\"📖 Page by page! [{workflow_name}]({run_url}) is reading through this {event_type}...\",\"runSuccess\":\"📚 TL;DR ready! [{workflow_name}]({run_url}) has distilled the essence. Knowledge condensed! ✨\",\"runFailure\":\"📖 Reading interrupted! [{workflow_name}]({run_url}) {status}. The document remains unsummarized...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index 179db50cdca..2041cae98d3 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -7580,7 +7580,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "plan" + GH_AW_WORKFLOW_NAME: "Plan Command" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -8275,8 +8277,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[plan] " GH_AW_ISSUE_LABELS: "plan,ai-generated" - GH_AW_WORKFLOW_NAME: "Plan Command" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8575,8 +8575,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Plan Command" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 93897b60031..dec932489db 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -7995,7 +7995,12 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: "gpt-5" + GH_AW_SAFE_OUTPUTS_STAGED: "true" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Verses penned by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎭 Hear ye! The muse stirs! [{workflow_name}]({run_url}) takes quill in hand for this {event_type}...\",\"runSuccess\":\"🪶 The poem is writ! [{workflow_name}]({run_url}) has composed verses most fair. Applause! 👏\",\"runFailure\":\"🎭 Alas! [{workflow_name}]({run_url}) {status}. The muse has fled, leaving verses unsung...\"}" + GH_AW_WORKFLOW_ID: "poem-bot" + GH_AW_WORKFLOW_NAME: "Poem Bot - A Creative Agentic Workflow" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -10900,11 +10905,7 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[🎭 POEM-BOT] " GH_AW_ISSUE_LABELS: "poetry,automation,ai-generated" - GH_AW_WORKFLOW_NAME: "Poem Bot - A Creative Agentic Workflow" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5" GH_AW_SAFE_OUTPUTS_STAGED: "true" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Verses penned by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎭 Hear ye! The muse stirs! [{workflow_name}]({run_url}) takes quill in hand for this {event_type}...\",\"runSuccess\":\"🪶 The poem is writ! [{workflow_name}]({run_url}) has composed verses most fair. Applause! 👏\",\"runFailure\":\"🎭 Alas! [{workflow_name}]({run_url}) {status}. The muse has fled, leaving verses unsung...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -11203,11 +11204,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Poem Bot - A Creative Agentic Workflow" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5" GH_AW_SAFE_OUTPUTS_STAGED: "true" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Verses penned by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎭 Hear ye! The muse stirs! [{workflow_name}]({run_url}) takes quill in hand for this {event_type}...\",\"runSuccess\":\"🪶 The poem is writ! [{workflow_name}]({run_url}) has composed verses most fair. Applause! 👏\",\"runFailure\":\"🎭 Alas! [{workflow_name}]({run_url}) {status}. The muse has fled, leaving verses unsung...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -11501,11 +11498,7 @@ jobs: GH_AW_MAX_PATCH_SIZE: 1024 GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} - GH_AW_WORKFLOW_NAME: "Poem Bot - A Creative Agentic Workflow" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5" GH_AW_SAFE_OUTPUTS_STAGED: "true" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Verses penned by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎭 Hear ye! The muse stirs! [{workflow_name}]({run_url}) takes quill in hand for this {event_type}...\",\"runSuccess\":\"🪶 The poem is writ! [{workflow_name}]({run_url}) has composed verses most fair. Applause! 👏\",\"runFailure\":\"🎭 Alas! [{workflow_name}]({run_url}) {status}. The muse has fled, leaving verses unsung...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -12008,11 +12001,7 @@ jobs: GH_AW_CREATED_DISCUSSION_NUMBER: ${{ steps.create_discussion.outputs.discussion_number }} GH_AW_CREATED_PULL_REQUEST_URL: ${{ steps.create_pull_request.outputs.pull_request_url }} GH_AW_CREATED_PULL_REQUEST_NUMBER: ${{ steps.create_pull_request.outputs.pull_request_number }} - GH_AW_WORKFLOW_NAME: "Poem Bot - A Creative Agentic Workflow" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5" GH_AW_SAFE_OUTPUTS_STAGED: "true" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Verses penned by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎭 Hear ye! The muse stirs! [{workflow_name}]({run_url}) takes quill in hand for this {event_type}...\",\"runSuccess\":\"🪶 The poem is writ! [{workflow_name}]({run_url}) has composed verses most fair. Applause! 👏\",\"runFailure\":\"🎭 Alas! [{workflow_name}]({run_url}) {status}. The muse has fled, leaving verses unsung...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -12420,11 +12409,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Poem Bot - A Creative Agentic Workflow" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5" GH_AW_SAFE_OUTPUTS_STAGED: "true" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Verses penned by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎭 Hear ye! The muse stirs! [{workflow_name}]({run_url}) takes quill in hand for this {event_type}...\",\"runSuccess\":\"🪶 The poem is writ! [{workflow_name}]({run_url}) has composed verses most fair. Applause! 👏\",\"runFailure\":\"🎭 Alas! [{workflow_name}]({run_url}) {status}. The muse has fled, leaving verses unsung...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -12478,11 +12463,7 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_PR_REVIEW_COMMENT_SIDE: "RIGHT" - GH_AW_WORKFLOW_NAME: "Poem Bot - A Creative Agentic Workflow" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5" GH_AW_SAFE_OUTPUTS_STAGED: "true" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Verses penned by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎭 Hear ye! The muse stirs! [{workflow_name}]({run_url}) takes quill in hand for this {event_type}...\",\"runSuccess\":\"🪶 The poem is writ! [{workflow_name}]({run_url}) has composed verses most fair. Applause! 👏\",\"runFailure\":\"🎭 Alas! [{workflow_name}]({run_url}) {status}. The muse has fled, leaving verses unsung...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -12694,11 +12675,7 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_LABELS_ALLOWED: "poetry,creative,automation,ai-generated,epic,haiku,sonnet,limerick" GH_AW_LABELS_MAX_COUNT: 5 - GH_AW_WORKFLOW_NAME: "Poem Bot - A Creative Agentic Workflow" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5" GH_AW_SAFE_OUTPUTS_STAGED: "true" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Verses penned by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎭 Hear ye! The muse stirs! [{workflow_name}]({run_url}) takes quill in hand for this {event_type}...\",\"runSuccess\":\"🪶 The poem is writ! [{workflow_name}]({run_url}) has composed verses most fair. Applause! 👏\",\"runFailure\":\"🎭 Alas! [{workflow_name}]({run_url}) {status}. The muse has fled, leaving verses unsung...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -12819,11 +12796,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Poem Bot - A Creative Agentic Workflow" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5" GH_AW_SAFE_OUTPUTS_STAGED: "true" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Verses penned by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎭 Hear ye! The muse stirs! [{workflow_name}]({run_url}) takes quill in hand for this {event_type}...\",\"runSuccess\":\"🪶 The poem is writ! [{workflow_name}]({run_url}) has composed verses most fair. Applause! 👏\",\"runFailure\":\"🎭 Alas! [{workflow_name}]({run_url}) {status}. The muse has fled, leaving verses unsung...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -12880,11 +12853,7 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_PUSH_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Poem Bot - A Creative Agentic Workflow" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5" GH_AW_SAFE_OUTPUTS_STAGED: "true" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Verses penned by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎭 Hear ye! The muse stirs! [{workflow_name}]({run_url}) takes quill in hand for this {event_type}...\",\"runSuccess\":\"🪶 The poem is writ! [{workflow_name}]({run_url}) has composed verses most fair. Applause! 👏\",\"runFailure\":\"🎭 Alas! [{workflow_name}]({run_url}) {status}. The muse has fled, leaving verses unsung...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -13198,11 +13167,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Poem Bot - A Creative Agentic Workflow" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5" GH_AW_SAFE_OUTPUTS_STAGED: "true" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Verses penned by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎭 Hear ye! The muse stirs! [{workflow_name}]({run_url}) takes quill in hand for this {event_type}...\",\"runSuccess\":\"🪶 The poem is writ! [{workflow_name}]({run_url}) has composed verses most fair. Applause! 👏\",\"runFailure\":\"🎭 Alas! [{workflow_name}]({run_url}) {status}. The muse has fled, leaving verses unsung...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -13341,11 +13306,7 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_CREATED_ISSUE_NUMBER: ${{ steps.create_issue.outputs.issue_number }} GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }} - GH_AW_WORKFLOW_NAME: "Poem Bot - A Creative Agentic Workflow" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5" GH_AW_SAFE_OUTPUTS_STAGED: "true" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Verses penned by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎭 Hear ye! The muse stirs! [{workflow_name}]({run_url}) takes quill in hand for this {event_type}...\",\"runSuccess\":\"🪶 The poem is writ! [{workflow_name}]({run_url}) has composed verses most fair. Applause! 👏\",\"runFailure\":\"🎭 Alas! [{workflow_name}]({run_url}) {status}. The muse has fled, leaving verses unsung...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -13658,11 +13619,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Poem Bot - A Creative Agentic Workflow" - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "gpt-5" GH_AW_SAFE_OUTPUTS_STAGED: "true" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🪶 *Verses penned by [{workflow_name}]({run_url})*\",\"runStarted\":\"🎭 Hear ye! The muse stirs! [{workflow_name}]({run_url}) takes quill in hand for this {event_type}...\",\"runSuccess\":\"🪶 The poem is writ! [{workflow_name}]({run_url}) has composed verses most fair. Applause! 👏\",\"runFailure\":\"🎭 Alas! [{workflow_name}]({run_url}) {status}. The muse has fled, leaving verses unsung...\"}" with: github-token: ${{ secrets.COPILOT_GITHUB_TOKEN || secrets.GH_AW_GITHUB_TOKEN }} script: | diff --git a/.github/workflows/portfolio-analyst.lock.yml b/.github/workflows/portfolio-analyst.lock.yml index 15fdd5dfc0a..05ef2f06cff 100644 --- a/.github/workflows/portfolio-analyst.lock.yml +++ b/.github/workflows/portfolio-analyst.lock.yml @@ -7489,7 +7489,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "portfolio-analyst-weekly" + GH_AW_WORKFLOW_ID: "portfolio-analyst" + GH_AW_WORKFLOW_NAME: "Automated Portfolio Analyst" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8392,9 +8395,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Automated Portfolio Analyst" - GH_AW_TRACKER_ID: "portfolio-analyst-weekly" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8679,9 +8679,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Automated Portfolio Analyst" - GH_AW_TRACKER_ID: "portfolio-analyst-weekly" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/pr-nitpick-reviewer.lock.yml b/.github/workflows/pr-nitpick-reviewer.lock.yml index ece732630d7..ebe4d431a0c 100644 --- a/.github/workflows/pr-nitpick-reviewer.lock.yml +++ b/.github/workflows/pr-nitpick-reviewer.lock.yml @@ -7623,7 +7623,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔍 *Meticulously inspected by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔬 Adjusting monocle... [{workflow_name}]({run_url}) is scrutinizing every pixel of this {event_type}...\",\"runSuccess\":\"🔍 Nitpicks catalogued! [{workflow_name}]({run_url}) has documented all the tiny details. Perfection awaits! ✅\",\"runFailure\":\"🔬 Lens cracked! [{workflow_name}]({run_url}) {status}. Some nitpicks remain undetected...\"}" + GH_AW_WORKFLOW_ID: "pr-nitpick-reviewer" + GH_AW_WORKFLOW_NAME: "PR Nitpick Reviewer 🔍" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8869,9 +8872,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "PR Nitpick Reviewer 🔍" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔍 *Meticulously inspected by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔬 Adjusting monocle... [{workflow_name}]({run_url}) is scrutinizing every pixel of this {event_type}...\",\"runSuccess\":\"🔍 Nitpicks catalogued! [{workflow_name}]({run_url}) has documented all the tiny details. Perfection awaits! ✅\",\"runFailure\":\"🔬 Lens cracked! [{workflow_name}]({run_url}) {status}. Some nitpicks remain undetected...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9158,9 +9158,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_CREATED_DISCUSSION_URL: ${{ steps.create_discussion.outputs.discussion_url }} GH_AW_CREATED_DISCUSSION_NUMBER: ${{ steps.create_discussion.outputs.discussion_number }} - GH_AW_WORKFLOW_NAME: "PR Nitpick Reviewer 🔍" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔍 *Meticulously inspected by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔬 Adjusting monocle... [{workflow_name}]({run_url}) is scrutinizing every pixel of this {event_type}...\",\"runSuccess\":\"🔍 Nitpicks catalogued! [{workflow_name}]({run_url}) has documented all the tiny details. Perfection awaits! ✅\",\"runFailure\":\"🔬 Lens cracked! [{workflow_name}]({run_url}) {status}. Some nitpicks remain undetected...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9569,9 +9566,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_PR_REVIEW_COMMENT_SIDE: "RIGHT" - GH_AW_WORKFLOW_NAME: "PR Nitpick Reviewer 🔍" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔍 *Meticulously inspected by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔬 Adjusting monocle... [{workflow_name}]({run_url}) is scrutinizing every pixel of this {event_type}...\",\"runSuccess\":\"🔍 Nitpicks catalogued! [{workflow_name}]({run_url}) has documented all the tiny details. Perfection awaits! ✅\",\"runFailure\":\"🔬 Lens cracked! [{workflow_name}]({run_url}) {status}. Some nitpicks remain undetected...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index b1ca4a7b2b8..ddef365483e 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -7094,7 +7094,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "prompt-clustering-analysis" + GH_AW_WORKFLOW_NAME: "Copilot Agent Prompt Clustering Analysis" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7997,8 +7999,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Copilot Agent Prompt Clustering Analysis" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml index b147ee26230..2ab687d9991 100644 --- a/.github/workflows/python-data-charts.lock.yml +++ b/.github/workflows/python-data-charts.lock.yml @@ -7520,7 +7520,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "python-data-charts" + GH_AW_WORKFLOW_NAME: "Python Data Visualization Generator" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -8423,8 +8425,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Python Data Visualization Generator" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8709,8 +8709,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Python Data Visualization Generator" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index c33354b841b..13c22b7d233 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -7919,7 +7919,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🎩 *Equipped by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔧 Pay attention, 007! [{workflow_name}]({run_url}) is preparing your gadgets for this {event_type}...\",\"runSuccess\":\"🎩 Mission equipment ready! [{workflow_name}]({run_url}) has optimized your workflow. Use wisely, 007! 🔫\",\"runFailure\":\"🔧 Technical difficulties! [{workflow_name}]({run_url}) {status}. Even Q Branch has bad days...\"}" + GH_AW_WORKFLOW_ID: "q" + GH_AW_WORKFLOW_NAME: "Q" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8798,9 +8801,6 @@ jobs: GH_AW_MAX_PATCH_SIZE: 1024 GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} - GH_AW_WORKFLOW_NAME: "Q" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🎩 *Equipped by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔧 Pay attention, 007! [{workflow_name}]({run_url}) is preparing your gadgets for this {event_type}...\",\"runSuccess\":\"🎩 Mission equipment ready! [{workflow_name}]({run_url}) has optimized your workflow. Use wisely, 007! 🔫\",\"runFailure\":\"🔧 Technical difficulties! [{workflow_name}]({run_url}) {status}. Even Q Branch has bad days...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9297,9 +9297,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_CREATED_PULL_REQUEST_URL: ${{ steps.create_pull_request.outputs.pull_request_url }} GH_AW_CREATED_PULL_REQUEST_NUMBER: ${{ steps.create_pull_request.outputs.pull_request_number }} - GH_AW_WORKFLOW_NAME: "Q" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🎩 *Equipped by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔧 Pay attention, 007! [{workflow_name}]({run_url}) is preparing your gadgets for this {event_type}...\",\"runSuccess\":\"🎩 Mission equipment ready! [{workflow_name}]({run_url}) has optimized your workflow. Use wisely, 007! 🔫\",\"runFailure\":\"🔧 Technical difficulties! [{workflow_name}]({run_url}) {status}. Even Q Branch has bad days...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/release.lock.yml b/.github/workflows/release.lock.yml index 172f58f8a00..61edf77e3ef 100644 --- a/.github/workflows/release.lock.yml +++ b/.github/workflows/release.lock.yml @@ -6744,7 +6744,9 @@ jobs: contents: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "release" + GH_AW_WORKFLOW_NAME: "Release" steps: - name: Download agent output artifact continue-on-error: true @@ -6899,8 +6901,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Release" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/repo-tree-map.lock.yml b/.github/workflows/repo-tree-map.lock.yml index 531080de796..32c39600bf0 100644 --- a/.github/workflows/repo-tree-map.lock.yml +++ b/.github/workflows/repo-tree-map.lock.yml @@ -6517,7 +6517,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "repo-tree-map" + GH_AW_WORKFLOW_NAME: "Repository Tree Map Generator" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7420,8 +7422,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Repository Tree Map Generator" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/repository-quality-improver.lock.yml b/.github/workflows/repository-quality-improver.lock.yml index 7e71bfc8555..9bcbe613005 100644 --- a/.github/workflows/repository-quality-improver.lock.yml +++ b/.github/workflows/repository-quality-improver.lock.yml @@ -7075,7 +7075,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "repository-quality-improver" + GH_AW_WORKFLOW_NAME: "Repository Quality Improvement Agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7978,8 +7980,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Repository Quality Improvement Agent" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/research.lock.yml b/.github/workflows/research.lock.yml index 265cca0298e..9209b3989bc 100644 --- a/.github/workflows/research.lock.yml +++ b/.github/workflows/research.lock.yml @@ -6485,7 +6485,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "research" + GH_AW_WORKFLOW_NAME: "Basic Research Agent" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7388,8 +7390,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Basic Research Agent" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml index 326458cf5e1..be36f3e4532 100644 --- a/.github/workflows/safe-output-health.lock.yml +++ b/.github/workflows/safe-output-health.lock.yml @@ -6628,7 +6628,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "safe-output-health" + GH_AW_WORKFLOW_NAME: "Safe Output Health Monitor" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7531,8 +7533,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Safe Output Health Monitor" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml index 610091fecf0..8187423a9ff 100644 --- a/.github/workflows/schema-consistency-checker.lock.yml +++ b/.github/workflows/schema-consistency-checker.lock.yml @@ -6395,7 +6395,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "schema-consistency-checker" + GH_AW_WORKFLOW_NAME: "Schema Consistency Checker" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7298,8 +7300,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Schema Consistency Checker" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 82bd1a8558a..c74f4d9e6fd 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -7481,7 +7481,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔭 *Intelligence gathered by [{workflow_name}]({run_url})*\",\"runStarted\":\"🏕️ Scout on patrol! [{workflow_name}]({run_url}) is blazing trails through this {event_type}...\",\"runSuccess\":\"🔭 Recon complete! [{workflow_name}]({run_url}) has charted the territory. Map ready! 🗺️\",\"runFailure\":\"🏕️ Lost in the wilderness! [{workflow_name}]({run_url}) {status}. Sending search party...\"}" + GH_AW_WORKFLOW_ID: "scout" + GH_AW_WORKFLOW_NAME: "Scout" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8108,9 +8111,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Scout" - GH_AW_ENGINE_ID: "claude" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔭 *Intelligence gathered by [{workflow_name}]({run_url})*\",\"runStarted\":\"🏕️ Scout on patrol! [{workflow_name}]({run_url}) is blazing trails through this {event_type}...\",\"runSuccess\":\"🔭 Recon complete! [{workflow_name}]({run_url}) has charted the territory. Map ready! 🗺️\",\"runFailure\":\"🏕️ Lost in the wilderness! [{workflow_name}]({run_url}) {status}. Sending search party...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/security-compliance.lock.yml b/.github/workflows/security-compliance.lock.yml index 0e215ace9f9..54bb39f4f30 100644 --- a/.github/workflows/security-compliance.lock.yml +++ b/.github/workflows/security-compliance.lock.yml @@ -6900,7 +6900,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "security-compliance" + GH_AW_WORKFLOW_NAME: "Security Compliance Campaign" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -7562,8 +7564,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_LABELS: "security,campaign-tracker" - GH_AW_WORKFLOW_NAME: "Security Compliance Campaign" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml index 357c9dea666..4886bcde3bb 100644 --- a/.github/workflows/security-fix-pr.lock.yml +++ b/.github/workflows/security-fix-pr.lock.yml @@ -6425,7 +6425,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "security-fix-pr" + GH_AW_WORKFLOW_NAME: "Security Fix PR" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -6723,8 +6725,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Security Fix PR" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index 71d89a8a98b..6cb99553c90 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -6642,7 +6642,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "semantic-function-refactor" + GH_AW_WORKFLOW_NAME: "Semantic Function Refactoring" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -7735,8 +7737,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[refactor] " GH_AW_ISSUE_LABELS: "refactoring,code-quality,automated-analysis" - GH_AW_WORKFLOW_NAME: "Semantic Function Refactoring" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8035,8 +8035,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Semantic Function Refactoring" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/slide-deck-maintainer.lock.yml b/.github/workflows/slide-deck-maintainer.lock.yml index 185db14133a..3f30a40fecb 100644 --- a/.github/workflows/slide-deck-maintainer.lock.yml +++ b/.github/workflows/slide-deck-maintainer.lock.yml @@ -6922,7 +6922,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "slide-deck-maintainer" + GH_AW_WORKFLOW_ID: "slide-deck-maintainer" + GH_AW_WORKFLOW_NAME: "Slide Deck Maintainer" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7219,9 +7222,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Slide Deck Maintainer" - GH_AW_TRACKER_ID: "slide-deck-maintainer" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 227f5ed7fbf..9d5577c30cd 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -6930,7 +6930,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 💥 *[THE END] — Illustrated by [{workflow_name}]({run_url})*\",\"runStarted\":\"💥 **WHOOSH!** [{workflow_name}]({run_url}) springs into action on this {event_type}! *[Panel 1 begins...]*\",\"runSuccess\":\"🎬 **THE END** — [{workflow_name}]({run_url}) **MISSION: ACCOMPLISHED!** The hero saves the day! ✨\",\"runFailure\":\"💫 **TO BE CONTINUED...** [{workflow_name}]({run_url}) {status}! Our hero faces unexpected challenges...\"}" + GH_AW_WORKFLOW_ID: "smoke-claude" + GH_AW_WORKFLOW_NAME: "Smoke Claude" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8494,9 +8497,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_EXPIRES: "1" - GH_AW_WORKFLOW_NAME: "Smoke Claude" - GH_AW_ENGINE_ID: "claude" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 💥 *[THE END] — Illustrated by [{workflow_name}]({run_url})*\",\"runStarted\":\"💥 **WHOOSH!** [{workflow_name}]({run_url}) springs into action on this {event_type}! *[Panel 1 begins...]*\",\"runSuccess\":\"🎬 **THE END** — [{workflow_name}]({run_url}) **MISSION: ACCOMPLISHED!** The hero saves the day! ✨\",\"runFailure\":\"💫 **TO BE CONTINUED...** [{workflow_name}]({run_url}) {status}! Our hero faces unexpected challenges...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8799,9 +8799,6 @@ jobs: GH_AW_CREATED_ISSUE_URL: ${{ steps.create_issue.outputs.issue_url }} GH_AW_CREATED_ISSUE_NUMBER: ${{ steps.create_issue.outputs.issue_number }} GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }} - GH_AW_WORKFLOW_NAME: "Smoke Claude" - GH_AW_ENGINE_ID: "claude" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 💥 *[THE END] — Illustrated by [{workflow_name}]({run_url})*\",\"runStarted\":\"💥 **WHOOSH!** [{workflow_name}]({run_url}) springs into action on this {event_type}! *[Panel 1 begins...]*\",\"runSuccess\":\"🎬 **THE END** — [{workflow_name}]({run_url}) **MISSION: ACCOMPLISHED!** The hero saves the day! ✨\",\"runFailure\":\"💫 **TO BE CONTINUED...** [{workflow_name}]({run_url}) {status}! Our hero faces unexpected challenges...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9210,9 +9207,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_LABELS_ALLOWED: "smoke-claude" - GH_AW_WORKFLOW_NAME: "Smoke Claude" - GH_AW_ENGINE_ID: "claude" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 💥 *[THE END] — Illustrated by [{workflow_name}]({run_url})*\",\"runStarted\":\"💥 **WHOOSH!** [{workflow_name}]({run_url}) springs into action on this {event_type}! *[Panel 1 begins...]*\",\"runSuccess\":\"🎬 **THE END** — [{workflow_name}]({run_url}) **MISSION: ACCOMPLISHED!** The hero saves the day! ✨\",\"runFailure\":\"💫 **TO BE CONTINUED...** [{workflow_name}]({run_url}) {status}! Our hero faces unexpected challenges...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-codex-firewall.lock.yml b/.github/workflows/smoke-codex-firewall.lock.yml index 913095040d1..83fa9c9b2bb 100644 --- a/.github/workflows/smoke-codex-firewall.lock.yml +++ b/.github/workflows/smoke-codex-firewall.lock.yml @@ -6987,7 +6987,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "codex" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔥 *Firewall tested by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔒 Initiating firewall smoke test... [{workflow_name}]({run_url}) is validating network sandboxing for {event_type}...\",\"runSuccess\":\"✅ Firewall validation complete... [{workflow_name}]({run_url}) confirmed network sandboxing is operational. 🛡️\",\"runFailure\":\"❌ Firewall validation failed... [{workflow_name}]({run_url}) {status}. Network sandboxing may not be working correctly.\"}" + GH_AW_WORKFLOW_ID: "smoke-codex-firewall" + GH_AW_WORKFLOW_NAME: "Smoke Codex Firewall" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8551,9 +8554,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_EXPIRES: "1" - GH_AW_WORKFLOW_NAME: "Smoke Codex Firewall" - GH_AW_ENGINE_ID: "codex" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔥 *Firewall tested by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔒 Initiating firewall smoke test... [{workflow_name}]({run_url}) is validating network sandboxing for {event_type}...\",\"runSuccess\":\"✅ Firewall validation complete... [{workflow_name}]({run_url}) confirmed network sandboxing is operational. 🛡️\",\"runFailure\":\"❌ Firewall validation failed... [{workflow_name}]({run_url}) {status}. Network sandboxing may not be working correctly.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8856,9 +8856,6 @@ jobs: GH_AW_CREATED_ISSUE_URL: ${{ steps.create_issue.outputs.issue_url }} GH_AW_CREATED_ISSUE_NUMBER: ${{ steps.create_issue.outputs.issue_number }} GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }} - GH_AW_WORKFLOW_NAME: "Smoke Codex Firewall" - GH_AW_ENGINE_ID: "codex" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔥 *Firewall tested by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔒 Initiating firewall smoke test... [{workflow_name}]({run_url}) is validating network sandboxing for {event_type}...\",\"runSuccess\":\"✅ Firewall validation complete... [{workflow_name}]({run_url}) confirmed network sandboxing is operational. 🛡️\",\"runFailure\":\"❌ Firewall validation failed... [{workflow_name}]({run_url}) {status}. Network sandboxing may not be working correctly.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9267,9 +9264,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_LABELS_ALLOWED: "smoke-codex-firewall" - GH_AW_WORKFLOW_NAME: "Smoke Codex Firewall" - GH_AW_ENGINE_ID: "codex" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔥 *Firewall tested by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔒 Initiating firewall smoke test... [{workflow_name}]({run_url}) is validating network sandboxing for {event_type}...\",\"runSuccess\":\"✅ Firewall validation complete... [{workflow_name}]({run_url}) confirmed network sandboxing is operational. 🛡️\",\"runFailure\":\"❌ Firewall validation failed... [{workflow_name}]({run_url}) {status}. Network sandboxing may not be working correctly.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9390,9 +9384,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Smoke Codex Firewall" - GH_AW_ENGINE_ID: "codex" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔥 *Firewall tested by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔒 Initiating firewall smoke test... [{workflow_name}]({run_url}) is validating network sandboxing for {event_type}...\",\"runSuccess\":\"✅ Firewall validation complete... [{workflow_name}]({run_url}) confirmed network sandboxing is operational. 🛡️\",\"runFailure\":\"❌ Firewall validation failed... [{workflow_name}]({run_url}) {status}. Network sandboxing may not be working correctly.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 68673ac4f58..6a46d36df45 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -7100,7 +7100,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "codex" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔮 *The oracle has spoken through [{workflow_name}]({run_url})*\",\"runStarted\":\"🔮 The ancient spirits stir... [{workflow_name}]({run_url}) awakens to divine this {event_type}...\",\"runSuccess\":\"✨ The prophecy is fulfilled... [{workflow_name}]({run_url}) has completed its mystical journey. The stars align. 🌟\",\"runFailure\":\"🌑 The shadows whisper... [{workflow_name}]({run_url}) {status}. The oracle requires further meditation...\"}" + GH_AW_WORKFLOW_ID: "smoke-codex" + GH_AW_WORKFLOW_NAME: "Smoke Codex" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8664,9 +8667,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_EXPIRES: "1" - GH_AW_WORKFLOW_NAME: "Smoke Codex" - GH_AW_ENGINE_ID: "codex" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔮 *The oracle has spoken through [{workflow_name}]({run_url})*\",\"runStarted\":\"🔮 The ancient spirits stir... [{workflow_name}]({run_url}) awakens to divine this {event_type}...\",\"runSuccess\":\"✨ The prophecy is fulfilled... [{workflow_name}]({run_url}) has completed its mystical journey. The stars align. 🌟\",\"runFailure\":\"🌑 The shadows whisper... [{workflow_name}]({run_url}) {status}. The oracle requires further meditation...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8969,9 +8969,6 @@ jobs: GH_AW_CREATED_ISSUE_URL: ${{ steps.create_issue.outputs.issue_url }} GH_AW_CREATED_ISSUE_NUMBER: ${{ steps.create_issue.outputs.issue_number }} GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }} - GH_AW_WORKFLOW_NAME: "Smoke Codex" - GH_AW_ENGINE_ID: "codex" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔮 *The oracle has spoken through [{workflow_name}]({run_url})*\",\"runStarted\":\"🔮 The ancient spirits stir... [{workflow_name}]({run_url}) awakens to divine this {event_type}...\",\"runSuccess\":\"✨ The prophecy is fulfilled... [{workflow_name}]({run_url}) has completed its mystical journey. The stars align. 🌟\",\"runFailure\":\"🌑 The shadows whisper... [{workflow_name}]({run_url}) {status}. The oracle requires further meditation...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9380,9 +9377,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_LABELS_ALLOWED: "smoke-codex" - GH_AW_WORKFLOW_NAME: "Smoke Codex" - GH_AW_ENGINE_ID: "codex" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔮 *The oracle has spoken through [{workflow_name}]({run_url})*\",\"runStarted\":\"🔮 The ancient spirits stir... [{workflow_name}]({run_url}) awakens to divine this {event_type}...\",\"runSuccess\":\"✨ The prophecy is fulfilled... [{workflow_name}]({run_url}) has completed its mystical journey. The stars align. 🌟\",\"runFailure\":\"🌑 The shadows whisper... [{workflow_name}]({run_url}) {status}. The oracle requires further meditation...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9503,9 +9497,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Smoke Codex" - GH_AW_ENGINE_ID: "codex" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔮 *The oracle has spoken through [{workflow_name}]({run_url})*\",\"runStarted\":\"🔮 The ancient spirits stir... [{workflow_name}]({run_url}) awakens to divine this {event_type}...\",\"runSuccess\":\"✨ The prophecy is fulfilled... [{workflow_name}]({run_url}) has completed its mystical journey. The stars align. 🌟\",\"runFailure\":\"🌑 The shadows whisper... [{workflow_name}]({run_url}) {status}. The oracle requires further meditation...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-copilot-no-firewall.lock.yml b/.github/workflows/smoke-copilot-no-firewall.lock.yml index a23e1ac6213..97f89378ac4 100644 --- a/.github/workflows/smoke-copilot-no-firewall.lock.yml +++ b/.github/workflows/smoke-copilot-no-firewall.lock.yml @@ -8280,7 +8280,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🤖 *DIAGNOSTIC REPORT GENERATED BY [{workflow_name}]({run_url})*\",\"runStarted\":\"🤖 SYSTEM_INIT: [{workflow_name}]({run_url}) ACTIVATED. PROCESSING {event_type}. ALL SUBSYSTEMS ONLINE.\",\"runSuccess\":\"🤖 DIAGNOSTIC COMPLETE: [{workflow_name}]({run_url}) STATUS: ALL_UNITS_OPERATIONAL. MISSION_SUCCESS.\",\"runFailure\":\"🤖 ALERT: [{workflow_name}]({run_url}) {status}. ANOMALY_DETECTED. REPAIR_REQUIRED.\"}" + GH_AW_WORKFLOW_ID: "smoke-copilot-no-firewall" + GH_AW_WORKFLOW_NAME: "Smoke Copilot No Firewall" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -9578,9 +9581,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_HIDE_OLDER_COMMENTS: "true" - GH_AW_WORKFLOW_NAME: "Smoke Copilot No Firewall" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🤖 *DIAGNOSTIC REPORT GENERATED BY [{workflow_name}]({run_url})*\",\"runStarted\":\"🤖 SYSTEM_INIT: [{workflow_name}]({run_url}) ACTIVATED. PROCESSING {event_type}. ALL SUBSYSTEMS ONLINE.\",\"runSuccess\":\"🤖 DIAGNOSTIC COMPLETE: [{workflow_name}]({run_url}) STATUS: ALL_UNITS_OPERATIONAL. MISSION_SUCCESS.\",\"runFailure\":\"🤖 ALERT: [{workflow_name}]({run_url}) {status}. ANOMALY_DETECTED. REPAIR_REQUIRED.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9989,9 +9989,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_LABELS_ALLOWED: "smoke-copilot-no-firewall" - GH_AW_WORKFLOW_NAME: "Smoke Copilot No Firewall" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🤖 *DIAGNOSTIC REPORT GENERATED BY [{workflow_name}]({run_url})*\",\"runStarted\":\"🤖 SYSTEM_INIT: [{workflow_name}]({run_url}) ACTIVATED. PROCESSING {event_type}. ALL SUBSYSTEMS ONLINE.\",\"runSuccess\":\"🤖 DIAGNOSTIC COMPLETE: [{workflow_name}]({run_url}) STATUS: ALL_UNITS_OPERATIONAL. MISSION_SUCCESS.\",\"runFailure\":\"🤖 ALERT: [{workflow_name}]({run_url}) {status}. ANOMALY_DETECTED. REPAIR_REQUIRED.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-copilot-playwright.lock.yml b/.github/workflows/smoke-copilot-playwright.lock.yml index 1e140f84b18..ece46e579c6 100644 --- a/.github/workflows/smoke-copilot-playwright.lock.yml +++ b/.github/workflows/smoke-copilot-playwright.lock.yml @@ -8573,7 +8573,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📰 *BREAKING: Report filed by [{workflow_name}]({run_url})*\",\"runStarted\":\"📰 BREAKING: [{workflow_name}]({run_url}) is now investigating this {event_type}. Sources say the story is developing...\",\"runSuccess\":\"📰 VERDICT: [{workflow_name}]({run_url}) has concluded. All systems operational. This is a developing story. 🎤\",\"runFailure\":\"📰 DEVELOPING STORY: [{workflow_name}]({run_url}) reports {status}. Our correspondents are investigating the incident...\"}" + GH_AW_WORKFLOW_ID: "smoke-copilot-playwright" + GH_AW_WORKFLOW_NAME: "Smoke Copilot Playwright" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -10137,9 +10140,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_EXPIRES: "1" - GH_AW_WORKFLOW_NAME: "Smoke Copilot Playwright" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📰 *BREAKING: Report filed by [{workflow_name}]({run_url})*\",\"runStarted\":\"📰 BREAKING: [{workflow_name}]({run_url}) is now investigating this {event_type}. Sources say the story is developing...\",\"runSuccess\":\"📰 VERDICT: [{workflow_name}]({run_url}) has concluded. All systems operational. This is a developing story. 🎤\",\"runFailure\":\"📰 DEVELOPING STORY: [{workflow_name}]({run_url}) reports {status}. Our correspondents are investigating the incident...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -10442,9 +10442,6 @@ jobs: GH_AW_CREATED_ISSUE_URL: ${{ steps.create_issue.outputs.issue_url }} GH_AW_CREATED_ISSUE_NUMBER: ${{ steps.create_issue.outputs.issue_number }} GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }} - GH_AW_WORKFLOW_NAME: "Smoke Copilot Playwright" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📰 *BREAKING: Report filed by [{workflow_name}]({run_url})*\",\"runStarted\":\"📰 BREAKING: [{workflow_name}]({run_url}) is now investigating this {event_type}. Sources say the story is developing...\",\"runSuccess\":\"📰 VERDICT: [{workflow_name}]({run_url}) has concluded. All systems operational. This is a developing story. 🎤\",\"runFailure\":\"📰 DEVELOPING STORY: [{workflow_name}]({run_url}) reports {status}. Our correspondents are investigating the incident...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -10853,9 +10850,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_LABELS_ALLOWED: "smoke-copilot" - GH_AW_WORKFLOW_NAME: "Smoke Copilot Playwright" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📰 *BREAKING: Report filed by [{workflow_name}]({run_url})*\",\"runStarted\":\"📰 BREAKING: [{workflow_name}]({run_url}) is now investigating this {event_type}. Sources say the story is developing...\",\"runSuccess\":\"📰 VERDICT: [{workflow_name}]({run_url}) has concluded. All systems operational. This is a developing story. 🎤\",\"runFailure\":\"📰 DEVELOPING STORY: [{workflow_name}]({run_url}) reports {status}. Our correspondents are investigating the incident...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-copilot-safe-inputs.lock.yml b/.github/workflows/smoke-copilot-safe-inputs.lock.yml index 0b19e4ddba3..4dcf0777f57 100644 --- a/.github/workflows/smoke-copilot-safe-inputs.lock.yml +++ b/.github/workflows/smoke-copilot-safe-inputs.lock.yml @@ -8254,7 +8254,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "smoke-copilot-safe-inputs" + GH_AW_WORKFLOW_NAME: "Smoke Copilot Safe Inputs" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -9552,8 +9554,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_HIDE_OLDER_COMMENTS: "true" - GH_AW_WORKFLOW_NAME: "Smoke Copilot Safe Inputs" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9962,8 +9962,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_LABELS_ALLOWED: "smoke-copilot" - GH_AW_WORKFLOW_NAME: "Smoke Copilot Safe Inputs" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 6fd78dbfbb4..e47ae4c2796 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -7095,7 +7095,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📰 *BREAKING: Report filed by [{workflow_name}]({run_url})*\",\"runStarted\":\"📰 BREAKING: [{workflow_name}]({run_url}) is now investigating this {event_type}. Sources say the story is developing...\",\"runSuccess\":\"📰 VERDICT: [{workflow_name}]({run_url}) has concluded. All systems operational. This is a developing story. 🎤\",\"runFailure\":\"📰 DEVELOPING STORY: [{workflow_name}]({run_url}) reports {status}. Our correspondents are investigating the incident...\"}" + GH_AW_WORKFLOW_ID: "smoke-copilot" + GH_AW_WORKFLOW_NAME: "Smoke Copilot" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8659,9 +8662,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_EXPIRES: "1" - GH_AW_WORKFLOW_NAME: "Smoke Copilot" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📰 *BREAKING: Report filed by [{workflow_name}]({run_url})*\",\"runStarted\":\"📰 BREAKING: [{workflow_name}]({run_url}) is now investigating this {event_type}. Sources say the story is developing...\",\"runSuccess\":\"📰 VERDICT: [{workflow_name}]({run_url}) has concluded. All systems operational. This is a developing story. 🎤\",\"runFailure\":\"📰 DEVELOPING STORY: [{workflow_name}]({run_url}) reports {status}. Our correspondents are investigating the incident...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8964,9 +8964,6 @@ jobs: GH_AW_CREATED_ISSUE_URL: ${{ steps.create_issue.outputs.issue_url }} GH_AW_CREATED_ISSUE_NUMBER: ${{ steps.create_issue.outputs.issue_number }} GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }} - GH_AW_WORKFLOW_NAME: "Smoke Copilot" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📰 *BREAKING: Report filed by [{workflow_name}]({run_url})*\",\"runStarted\":\"📰 BREAKING: [{workflow_name}]({run_url}) is now investigating this {event_type}. Sources say the story is developing...\",\"runSuccess\":\"📰 VERDICT: [{workflow_name}]({run_url}) has concluded. All systems operational. This is a developing story. 🎤\",\"runFailure\":\"📰 DEVELOPING STORY: [{workflow_name}]({run_url}) reports {status}. Our correspondents are investigating the incident...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9375,9 +9372,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_LABELS_ALLOWED: "smoke-copilot" - GH_AW_WORKFLOW_NAME: "Smoke Copilot" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📰 *BREAKING: Report filed by [{workflow_name}]({run_url})*\",\"runStarted\":\"📰 BREAKING: [{workflow_name}]({run_url}) is now investigating this {event_type}. Sources say the story is developing...\",\"runSuccess\":\"📰 VERDICT: [{workflow_name}]({run_url}) has concluded. All systems operational. This is a developing story. 🎤\",\"runFailure\":\"📰 DEVELOPING STORY: [{workflow_name}]({run_url}) reports {status}. Our correspondents are investigating the incident...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-detector.lock.yml b/.github/workflows/smoke-detector.lock.yml index 483ae841ed3..cfe83f42edc 100644 --- a/.github/workflows/smoke-detector.lock.yml +++ b/.github/workflows/smoke-detector.lock.yml @@ -7043,7 +7043,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🚨 *Alert generated by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔥 BEEP BEEP! [{workflow_name}]({run_url}) detected smoke on this {event_type}! Investigating...\",\"runSuccess\":\"🚨 All clear! [{workflow_name}]({run_url}) has completed the investigation. Fire report filed! 📋\",\"runFailure\":\"🔥 Alarm malfunction! [{workflow_name}]({run_url}) {status}. Manual inspection required...\"}" + GH_AW_WORKFLOW_ID: "smoke-detector" + GH_AW_WORKFLOW_NAME: "Smoke Detector - Smoke Test Failure Investigator" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8009,9 +8012,6 @@ jobs: GH_AW_ISSUE_TITLE_PREFIX: "[smoke-detector] " GH_AW_ISSUE_LABELS: "smoke-test,investigation" GH_AW_ISSUE_EXPIRES: "1" - GH_AW_WORKFLOW_NAME: "Smoke Detector - Smoke Test Failure Investigator" - GH_AW_ENGINE_ID: "claude" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🚨 *Alert generated by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔥 BEEP BEEP! [{workflow_name}]({run_url}) detected smoke on this {event_type}! Investigating...\",\"runSuccess\":\"🚨 All clear! [{workflow_name}]({run_url}) has completed the investigation. Fire report filed! 📋\",\"runFailure\":\"🔥 Alarm malfunction! [{workflow_name}]({run_url}) {status}. Manual inspection required...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8315,9 +8315,6 @@ jobs: GH_AW_CREATED_ISSUE_URL: ${{ steps.create_issue.outputs.issue_url }} GH_AW_CREATED_ISSUE_NUMBER: ${{ steps.create_issue.outputs.issue_number }} GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }} - GH_AW_WORKFLOW_NAME: "Smoke Detector - Smoke Test Failure Investigator" - GH_AW_ENGINE_ID: "claude" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🚨 *Alert generated by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔥 BEEP BEEP! [{workflow_name}]({run_url}) detected smoke on this {event_type}! Investigating...\",\"runSuccess\":\"🚨 All clear! [{workflow_name}]({run_url}) has completed the investigation. Fire report filed! 📋\",\"runFailure\":\"🔥 Alarm malfunction! [{workflow_name}]({run_url}) {status}. Manual inspection required...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/spec-kit-execute.lock.yml b/.github/workflows/spec-kit-execute.lock.yml index 15e33a3bf12..600fbab5cd0 100644 --- a/.github/workflows/spec-kit-execute.lock.yml +++ b/.github/workflows/spec-kit-execute.lock.yml @@ -7024,7 +7024,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "spec-kit-execute" + GH_AW_WORKFLOW_ID: "spec-kit-execute" + GH_AW_WORKFLOW_NAME: "Spec-Kit Execute" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7322,9 +7325,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Spec-Kit Execute" - GH_AW_TRACKER_ID: "spec-kit-execute" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/spec-kit-executor.lock.yml b/.github/workflows/spec-kit-executor.lock.yml index 52f1aaffafb..c3c8069263f 100644 --- a/.github/workflows/spec-kit-executor.lock.yml +++ b/.github/workflows/spec-kit-executor.lock.yml @@ -6899,7 +6899,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "spec-kit-executor" + GH_AW_WORKFLOW_ID: "spec-kit-executor" + GH_AW_WORKFLOW_NAME: "Spec Kit Executor" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7197,9 +7200,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Spec Kit Executor" - GH_AW_TRACKER_ID: "spec-kit-executor" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/speckit-dispatcher.lock.yml b/.github/workflows/speckit-dispatcher.lock.yml index de79f258175..a1db39bf30f 100644 --- a/.github/workflows/speckit-dispatcher.lock.yml +++ b/.github/workflows/speckit-dispatcher.lock.yml @@ -7828,7 +7828,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🎯 *Spec-Kit dispatcher by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔍 Analyzing your spec-kit request via [{workflow_name}]({run_url})...\",\"runSuccess\":\"✅ Guidance provided! [{workflow_name}]({run_url}) has determined the next steps.\",\"runFailure\":\"❌ Analysis incomplete. [{workflow_name}]({run_url}) {status}.\"}" + GH_AW_WORKFLOW_ID: "speckit-dispatcher" + GH_AW_WORKFLOW_NAME: "Spec-Kit Command Dispatcher" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8791,9 +8794,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Spec-Kit Command Dispatcher" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🎯 *Spec-Kit dispatcher by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔍 Analyzing your spec-kit request via [{workflow_name}]({run_url})...\",\"runSuccess\":\"✅ Guidance provided! [{workflow_name}]({run_url}) has determined the next steps.\",\"runFailure\":\"❌ Analysis incomplete. [{workflow_name}]({run_url}) {status}.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9095,9 +9095,6 @@ jobs: GH_AW_CREATED_ISSUE_URL: ${{ steps.create_issue.outputs.issue_url }} GH_AW_CREATED_ISSUE_NUMBER: ${{ steps.create_issue.outputs.issue_number }} GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }} - GH_AW_WORKFLOW_NAME: "Spec-Kit Command Dispatcher" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🎯 *Spec-Kit dispatcher by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔍 Analyzing your spec-kit request via [{workflow_name}]({run_url})...\",\"runSuccess\":\"✅ Guidance provided! [{workflow_name}]({run_url}) has determined the next steps.\",\"runFailure\":\"❌ Analysis incomplete. [{workflow_name}]({run_url}) {status}.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -9507,9 +9504,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_CREATED_ISSUE_NUMBER: ${{ steps.create_issue.outputs.issue_number }} GH_AW_TEMPORARY_ID_MAP: ${{ steps.create_issue.outputs.temporary_id_map }} - GH_AW_WORKFLOW_NAME: "Spec-Kit Command Dispatcher" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🎯 *Spec-Kit dispatcher by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔍 Analyzing your spec-kit request via [{workflow_name}]({run_url})...\",\"runSuccess\":\"✅ Guidance provided! [{workflow_name}]({run_url}) has determined the next steps.\",\"runFailure\":\"❌ Analysis incomplete. [{workflow_name}]({run_url}) {status}.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml index 8444816f964..645e00a323d 100644 --- a/.github/workflows/stale-repo-identifier.lock.yml +++ b/.github/workflows/stale-repo-identifier.lock.yml @@ -7449,7 +7449,10 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔍 *Analysis by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔍 Stale Repository Identifier starting! [{workflow_name}]({run_url}) is analyzing repository activity...\",\"runSuccess\":\"✅ Analysis complete! [{workflow_name}]({run_url}) has finished analyzing stale repositories.\",\"runFailure\":\"⚠️ Analysis interrupted! [{workflow_name}]({run_url}) {status}.\"}" + GH_AW_WORKFLOW_ID: "stale-repo-identifier" + GH_AW_WORKFLOW_NAME: "Stale Repository Identifier" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -8112,9 +8115,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[Stale Repository] " GH_AW_ISSUE_LABELS: "stale-repository,automated-analysis" - GH_AW_WORKFLOW_NAME: "Stale Repository Identifier" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔍 *Analysis by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔍 Stale Repository Identifier starting! [{workflow_name}]({run_url}) is analyzing repository activity...\",\"runSuccess\":\"✅ Analysis complete! [{workflow_name}]({run_url}) has finished analyzing stale repositories.\",\"runFailure\":\"⚠️ Analysis interrupted! [{workflow_name}]({run_url}) {status}.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8413,9 +8413,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Stale Repository Identifier" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔍 *Analysis by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔍 Stale Repository Identifier starting! [{workflow_name}]({run_url}) is analyzing repository activity...\",\"runSuccess\":\"✅ Analysis complete! [{workflow_name}]({run_url}) has finished analyzing stale repositories.\",\"runFailure\":\"⚠️ Analysis interrupted! [{workflow_name}]({run_url}) {status}.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index bc75dc63e35..d545ee852d1 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -6479,7 +6479,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "static-analysis-report" + GH_AW_WORKFLOW_NAME: "Static Analysis Report" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7382,8 +7384,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Static Analysis Report" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/sub-issue-closer.lock.yml b/.github/workflows/sub-issue-closer.lock.yml index d0073f1d86d..c78f776982a 100644 --- a/.github/workflows/sub-issue-closer.lock.yml +++ b/.github/workflows/sub-issue-closer.lock.yml @@ -6507,7 +6507,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "sub-issue-closer" + GH_AW_WORKFLOW_NAME: "Sub-Issue Closer" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -7687,8 +7689,6 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_COMMENT_TARGET: "*" - GH_AW_WORKFLOW_NAME: "Sub-Issue Closer" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8096,8 +8096,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Sub-Issue Closer" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/super-linter.lock.yml b/.github/workflows/super-linter.lock.yml index 856fd1a0ca4..4d5f7de2b73 100644 --- a/.github/workflows/super-linter.lock.yml +++ b/.github/workflows/super-linter.lock.yml @@ -6645,7 +6645,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "super-linter" + GH_AW_WORKFLOW_NAME: "Super Linter Report" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -7308,8 +7310,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[linter] " GH_AW_ISSUE_LABELS: "automation,code-quality" - GH_AW_WORKFLOW_NAME: "Super Linter Report" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index 7eeef8aee07..483fcde13cc 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -6928,7 +6928,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📝 *Documentation by [{workflow_name}]({run_url})*\",\"runStarted\":\"✍️ The Technical Writer begins! [{workflow_name}]({run_url}) is documenting this {event_type}...\",\"runSuccess\":\"📝 Documentation complete! [{workflow_name}]({run_url}) has written the docs. Clear as crystal! ✨\",\"runFailure\":\"✍️ Writer's block! [{workflow_name}]({run_url}) {status}. The page remains blank...\"}" + GH_AW_WORKFLOW_ID: "technical-doc-writer" + GH_AW_WORKFLOW_NAME: "Technical Doc Writer" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -7805,9 +7808,6 @@ jobs: GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_PR_ALLOW_EMPTY: "false" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Technical Doc Writer" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📝 *Documentation by [{workflow_name}]({run_url})*\",\"runStarted\":\"✍️ The Technical Writer begins! [{workflow_name}]({run_url}) is documenting this {event_type}...\",\"runSuccess\":\"📝 Documentation complete! [{workflow_name}]({run_url}) has written the docs. Clear as crystal! ✨\",\"runFailure\":\"✍️ Writer's block! [{workflow_name}]({run_url}) {status}. The page remains blank...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8304,9 +8304,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_CREATED_PULL_REQUEST_URL: ${{ steps.create_pull_request.outputs.pull_request_url }} GH_AW_CREATED_PULL_REQUEST_NUMBER: ${{ steps.create_pull_request.outputs.pull_request_number }} - GH_AW_WORKFLOW_NAME: "Technical Doc Writer" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📝 *Documentation by [{workflow_name}]({run_url})*\",\"runStarted\":\"✍️ The Technical Writer begins! [{workflow_name}]({run_url}) is documenting this {event_type}...\",\"runSuccess\":\"📝 Documentation complete! [{workflow_name}]({run_url}) has written the docs. Clear as crystal! ✨\",\"runFailure\":\"✍️ Writer's block! [{workflow_name}]({run_url}) {status}. The page remains blank...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8714,9 +8711,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Technical Doc Writer" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 📝 *Documentation by [{workflow_name}]({run_url})*\",\"runStarted\":\"✍️ The Technical Writer begins! [{workflow_name}]({run_url}) is documenting this {event_type}...\",\"runSuccess\":\"📝 Documentation complete! [{workflow_name}]({run_url}) has written the docs. Clear as crystal! ✨\",\"runFailure\":\"✍️ Writer's block! [{workflow_name}]({run_url}) {status}. The page remains blank...\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 42ca1e9d816..3de471374ea 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -7145,7 +7145,9 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "tidy" + GH_AW_WORKFLOW_NAME: "Tidy" outputs: create_pull_request_pull_request_number: ${{ steps.create_pull_request.outputs.pull_request_number }} create_pull_request_pull_request_url: ${{ steps.create_pull_request.outputs.pull_request_url }} @@ -7484,8 +7486,6 @@ jobs: GH_AW_MAX_PATCH_SIZE: 1024 GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} - GH_AW_WORKFLOW_NAME: "Tidy" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -7982,8 +7982,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_PUSH_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 - GH_AW_WORKFLOW_NAME: "Tidy" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/typist.lock.yml b/.github/workflows/typist.lock.yml index 9130ff5067a..9eed2acbf55 100644 --- a/.github/workflows/typist.lock.yml +++ b/.github/workflows/typist.lock.yml @@ -6640,7 +6640,9 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_WORKFLOW_ID: "typist" + GH_AW_WORKFLOW_NAME: "Typist - Go Type Analysis" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7543,8 +7545,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Typist - Go Type Analysis" - GH_AW_ENGINE_ID: "claude" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 9d6f8128256..2b59a2012e4 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -7197,7 +7197,10 @@ jobs: pull-requests: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "claude" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🗜️ *Compressed by [{workflow_name}]({run_url})*\",\"runStarted\":\"📦 Time to slim down! [{workflow_name}]({run_url}) is trimming the excess from this {event_type}...\",\"runSuccess\":\"🗜️ Docs on a diet! [{workflow_name}]({run_url}) has removed the bloat. Lean and mean! 💪\",\"runFailure\":\"📦 Unbloating paused! [{workflow_name}]({run_url}) {status}. The docs remain... fluffy.\"}" + GH_AW_WORKFLOW_ID: "unbloat-docs" + GH_AW_WORKFLOW_NAME: "Documentation Unbloat" outputs: add_comment_comment_id: ${{ steps.add_comment.outputs.comment_id }} add_comment_comment_url: ${{ steps.add_comment.outputs.comment_url }} @@ -8076,9 +8079,6 @@ jobs: GH_AW_MAX_PATCH_SIZE: 1024 GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} - GH_AW_WORKFLOW_NAME: "Documentation Unbloat" - GH_AW_ENGINE_ID: "claude" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🗜️ *Compressed by [{workflow_name}]({run_url})*\",\"runStarted\":\"📦 Time to slim down! [{workflow_name}]({run_url}) is trimming the excess from this {event_type}...\",\"runSuccess\":\"🗜️ Docs on a diet! [{workflow_name}]({run_url}) has removed the bloat. Lean and mean! 💪\",\"runFailure\":\"📦 Unbloating paused! [{workflow_name}]({run_url}) {status}. The docs remain... fluffy.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8575,9 +8575,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_CREATED_PULL_REQUEST_URL: ${{ steps.create_pull_request.outputs.pull_request_url }} GH_AW_CREATED_PULL_REQUEST_NUMBER: ${{ steps.create_pull_request.outputs.pull_request_number }} - GH_AW_WORKFLOW_NAME: "Documentation Unbloat" - GH_AW_ENGINE_ID: "claude" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🗜️ *Compressed by [{workflow_name}]({run_url})*\",\"runStarted\":\"📦 Time to slim down! [{workflow_name}]({run_url}) is trimming the excess from this {event_type}...\",\"runSuccess\":\"🗜️ Docs on a diet! [{workflow_name}]({run_url}) has removed the bloat. Lean and mean! 💪\",\"runFailure\":\"📦 Unbloating paused! [{workflow_name}]({run_url}) {status}. The docs remain... fluffy.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8985,9 +8982,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Documentation Unbloat" - GH_AW_ENGINE_ID: "claude" - GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🗜️ *Compressed by [{workflow_name}]({run_url})*\",\"runStarted\":\"📦 Time to slim down! [{workflow_name}]({run_url}) is trimming the excess from this {event_type}...\",\"runSuccess\":\"🗜️ Docs on a diet! [{workflow_name}]({run_url}) has removed the bloat. Lean and mean! 💪\",\"runFailure\":\"📦 Unbloating paused! [{workflow_name}]({run_url}) {status}. The docs remain... fluffy.\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/video-analyzer.lock.yml b/.github/workflows/video-analyzer.lock.yml index 31f3f1d2ca7..e3573c41872 100644 --- a/.github/workflows/video-analyzer.lock.yml +++ b/.github/workflows/video-analyzer.lock.yml @@ -6669,7 +6669,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "video-analyzer" + GH_AW_WORKFLOW_NAME: "Video Analysis Agent" outputs: create_issue_issue_number: ${{ steps.create_issue.outputs.issue_number }} create_issue_issue_url: ${{ steps.create_issue.outputs.issue_url }} @@ -7332,8 +7334,6 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_ISSUE_TITLE_PREFIX: "[video-analysis] " GH_AW_ISSUE_LABELS: "automation,video-processing" - GH_AW_WORKFLOW_NAME: "Video Analysis Agent" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/weekly-issue-summary.lock.yml b/.github/workflows/weekly-issue-summary.lock.yml index 28d351827cb..7b9d9c4e248 100644 --- a/.github/workflows/weekly-issue-summary.lock.yml +++ b/.github/workflows/weekly-issue-summary.lock.yml @@ -7091,7 +7091,10 @@ jobs: discussions: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_TRACKER_ID: "weekly-issue-summary" + GH_AW_WORKFLOW_ID: "weekly-issue-summary" + GH_AW_WORKFLOW_NAME: "Weekly Issue Summary" outputs: create_discussion_discussion_number: ${{ steps.create_discussion.outputs.discussion_number }} create_discussion_discussion_url: ${{ steps.create_discussion.outputs.discussion_url }} @@ -7994,9 +7997,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Weekly Issue Summary" - GH_AW_TRACKER_ID: "weekly-issue-summary" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -8281,9 +8281,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Weekly Issue Summary" - GH_AW_TRACKER_ID: "weekly-issue-summary" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/workflow-generator.lock.yml b/.github/workflows/workflow-generator.lock.yml index 88a80536d03..2b080166c5a 100644 --- a/.github/workflows/workflow-generator.lock.yml +++ b/.github/workflows/workflow-generator.lock.yml @@ -6705,7 +6705,9 @@ jobs: issues: write timeout-minutes: 15 env: - GH_AW_WORKFLOW_ID: "agent" + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "workflow-generator" + GH_AW_WORKFLOW_NAME: "Workflow Generator" outputs: assign_to_agent_assigned: ${{ steps.assign_to_agent.outputs.assigned }} steps: @@ -7798,8 +7800,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Workflow Generator" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_AGENT_TOKEN }} script: | @@ -7978,8 +7978,6 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Workflow Generator" - GH_AW_ENGINE_ID: "copilot" with: github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | From dd4350037a33e6dec5349dc59ac76708c7c02a86 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 14:04:15 +0000 Subject: [PATCH 7/7] Add GetWorkflowIDFromPath helper function - Created GetWorkflowIDFromPath() helper function in compiler_yaml_helpers.go - Updated compiler_safe_outputs_consolidated.go to use the helper function - Removed unused filepath and strings imports from compiler_safe_outputs_consolidated.go - Added comprehensive test coverage for GetWorkflowIDFromPath() - Renamed workflowFilename parameter to workflowID for clarity in buildJobLevelSafeOutputEnvVars() Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../compiler_safe_outputs_consolidated.go | 16 ++++---- pkg/workflow/compiler_yaml_helpers.go | 8 ++++ pkg/workflow/compiler_yaml_helpers_test.go | 41 +++++++++++++++++++ 3 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 pkg/workflow/compiler_yaml_helpers_test.go diff --git a/pkg/workflow/compiler_safe_outputs_consolidated.go b/pkg/workflow/compiler_safe_outputs_consolidated.go index 9cf0ee55bdb..c76a487e253 100644 --- a/pkg/workflow/compiler_safe_outputs_consolidated.go +++ b/pkg/workflow/compiler_safe_outputs_consolidated.go @@ -2,8 +2,6 @@ package workflow import ( "fmt" - "path/filepath" - "strings" "github.com/githubnext/gh-aw/pkg/constants" "github.com/githubnext/gh-aw/pkg/logger" @@ -282,7 +280,7 @@ func (c *Compiler) buildConsolidatedSafeOutputsJob(data *WorkflowData, mainJobNa // 9. Create Code Scanning Alert step if data.SafeOutputs.CreateCodeScanningAlerts != nil { - workflowFilename := strings.TrimSuffix(filepath.Base(markdownPath), ".md") + workflowFilename := GetWorkflowIDFromPath(markdownPath) stepConfig := c.buildCreateCodeScanningAlertStepConfig(data, mainJobName, threatDetectionEnabled, workflowFilename) stepYAML := c.buildConsolidatedSafeOutputStep(data, stepConfig) steps = append(steps, stepYAML...) @@ -498,11 +496,11 @@ func (c *Compiler) buildConsolidatedSafeOutputsJob(data *WorkflowData, mainJobNa needs = append(needs, constants.ActivationJobName) } - // Extract workflow filename (without extension) for GH_AW_WORKFLOW_ID - workflowFilename := strings.TrimSuffix(filepath.Base(markdownPath), ".md") + // Extract workflow ID from markdown path for GH_AW_WORKFLOW_ID + workflowID := GetWorkflowIDFromPath(markdownPath) // Build job-level environment variables that are common to all safe output steps - jobEnv := c.buildJobLevelSafeOutputEnvVars(data, workflowFilename) + jobEnv := c.buildJobLevelSafeOutputEnvVars(data, workflowID) job := &Job{ Name: "safe_outputs", @@ -587,12 +585,12 @@ func (c *Compiler) buildConsolidatedSafeOutputStep(data *WorkflowData, config Sa // buildJobLevelSafeOutputEnvVars builds environment variables that should be set at the job level // for the consolidated safe_outputs job. These are variables that are common to all safe output steps. -func (c *Compiler) buildJobLevelSafeOutputEnvVars(data *WorkflowData, workflowFilename string) map[string]string { +func (c *Compiler) buildJobLevelSafeOutputEnvVars(data *WorkflowData, workflowID string) map[string]string { envVars := make(map[string]string) - // Set GH_AW_WORKFLOW_ID to the workflow filename (without extension) + // Set GH_AW_WORKFLOW_ID to the workflow ID (filename without extension) // This is used for branch naming in create_pull_request and other operations - envVars["GH_AW_WORKFLOW_ID"] = fmt.Sprintf("%q", workflowFilename) + envVars["GH_AW_WORKFLOW_ID"] = fmt.Sprintf("%q", workflowID) // Add workflow metadata that's common to all steps envVars["GH_AW_WORKFLOW_NAME"] = fmt.Sprintf("%q", data.Name) diff --git a/pkg/workflow/compiler_yaml_helpers.go b/pkg/workflow/compiler_yaml_helpers.go index c99c79d924f..23d6a8c6b8b 100644 --- a/pkg/workflow/compiler_yaml_helpers.go +++ b/pkg/workflow/compiler_yaml_helpers.go @@ -2,6 +2,7 @@ package workflow import ( "fmt" + "path/filepath" "strings" "github.com/githubnext/gh-aw/pkg/constants" @@ -10,6 +11,13 @@ import ( var compilerYamlHelpersLog = logger.New("workflow:compiler_yaml_helpers") +// GetWorkflowIDFromPath extracts the workflow ID from a markdown file path. +// The workflow ID is the filename without the .md extension. +// Example: "/path/to/ai-moderator.md" -> "ai-moderator" +func GetWorkflowIDFromPath(markdownPath string) string { + return strings.TrimSuffix(filepath.Base(markdownPath), ".md") +} + // convertStepToYAML converts a step map to YAML format. // This is a method wrapper around the package-level ConvertStepToYAML function. func (c *Compiler) convertStepToYAML(stepMap map[string]any) (string, error) { diff --git a/pkg/workflow/compiler_yaml_helpers_test.go b/pkg/workflow/compiler_yaml_helpers_test.go new file mode 100644 index 00000000000..26cfd6bee30 --- /dev/null +++ b/pkg/workflow/compiler_yaml_helpers_test.go @@ -0,0 +1,41 @@ +package workflow + +import "testing" + +func TestGetWorkflowIDFromPath(t *testing.T) { + tests := []struct { + name string + path string + expected string + }{ + { + name: "simple filename", + path: "ai-moderator.md", + expected: "ai-moderator", + }, + { + name: "full path", + path: "/home/user/workflows/test-workflow.md", + expected: "test-workflow", + }, + { + name: "filename with multiple dots", + path: "/path/to/workflow.test.md", + expected: "workflow.test", + }, + { + name: "relative path", + path: ".github/workflows/daily-fact.md", + expected: "daily-fact", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := GetWorkflowIDFromPath(tt.path) + if result != tt.expected { + t.Errorf("GetWorkflowIDFromPath(%q) = %q, want %q", tt.path, result, tt.expected) + } + }) + } +}