diff --git a/.github/workflows/daily-security-red-team.lock.yml b/.github/workflows/daily-security-red-team.lock.yml index a3d9e5fe4f5..caee64b3975 100644 --- a/.github/workflows/daily-security-red-team.lock.yml +++ b/.github/workflows/daily-security-red-team.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"9d441a5d6c0d5eddf2053e7941722c1b02a12bc024b07011b7cc352ca7511bfc","strict":true,"agent_id":"claude"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5ac9d361c9968adc7585ddfe3152c16ae13d0db29c4dc89095c9dcd9a8ccb4e8","strict":true,"agent_id":"claude"} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_ENDPOINT","GH_AW_OTEL_HEADERS","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.35"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.35"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.35"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.35"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.3"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -200,21 +200,21 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_9ccdfe54846f1a39_EOF' + cat << 'GH_AW_PROMPT_7d98608d9d74e9db_EOF' - GH_AW_PROMPT_9ccdfe54846f1a39_EOF + GH_AW_PROMPT_7d98608d9d74e9db_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_9ccdfe54846f1a39_EOF' + cat << 'GH_AW_PROMPT_7d98608d9d74e9db_EOF' Tools: create_issue(max:5), create_discussion, missing_tool, missing_data, noop - GH_AW_PROMPT_9ccdfe54846f1a39_EOF + GH_AW_PROMPT_7d98608d9d74e9db_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_9ccdfe54846f1a39_EOF' + cat << 'GH_AW_PROMPT_7d98608d9d74e9db_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -243,15 +243,15 @@ jobs: {{/if}} - GH_AW_PROMPT_9ccdfe54846f1a39_EOF + GH_AW_PROMPT_7d98608d9d74e9db_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_9ccdfe54846f1a39_EOF' + cat << 'GH_AW_PROMPT_7d98608d9d74e9db_EOF' {{#runtime-import .github/workflows/shared/security-analysis-base.md}} {{#runtime-import .github/workflows/shared/reporting.md}} {{#runtime-import .github/workflows/shared/observability-otlp.md}} {{#runtime-import .github/workflows/daily-security-red-team.md}} - GH_AW_PROMPT_9ccdfe54846f1a39_EOF + GH_AW_PROMPT_7d98608d9d74e9db_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -331,6 +331,7 @@ jobs: /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/base + /tmp/gh-aw/.claude/agents if-no-files-found: ignore retention-days: 1 @@ -469,6 +470,11 @@ jobs: GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".claude/agents" + GH_AW_SUB_AGENT_EXT: ".md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.35 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.35 ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.35 ghcr.io/github/gh-aw-firewall/squid:0.25.35 ghcr.io/github/gh-aw-mcpg:v0.3.3 ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - name: Generate Safe Outputs Config @@ -476,9 +482,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_2bde814c0d14dfe7_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_02a524cecd8f0bba_EOF' {"create_discussion":{"category":"audits","close_older_discussions":true,"expires":72,"fallback_to_issue":true,"max":1,"title_prefix":"[security-red-team] "},"create_issue":{"labels":["security","red-team"],"max":5,"title_prefix":"🚨 [SECURITY]"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_2bde814c0d14dfe7_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_02a524cecd8f0bba_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -703,7 +709,7 @@ jobs: export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.3' GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_bbf797589361ca21_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_fb7ffea3188ba945_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -734,7 +740,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_bbf797589361ca21_EOF + GH_AW_MCP_CONFIG_fb7ffea3188ba945_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/.github/workflows/daily-security-red-team.md b/.github/workflows/daily-security-red-team.md index 3047bfec956..ae92bc9dcd5 100644 --- a/.github/workflows/daily-security-red-team.md +++ b/.github/workflows/daily-security-red-team.md @@ -23,6 +23,8 @@ safe-outputs: labels: ["security", "red-team"] max: 5 timeout-minutes: 60 +features: + inline-agents: true imports: - shared/security-analysis-base.md - uses: shared/daily-audit-base.md @@ -569,150 +571,15 @@ echo "📊 Total findings all time: $NEW_TOTAL_FINDINGS" For each finding, perform forensics to identify when the problematic code was introduced: -```bash -#!/bin/bash - -if [ ${#FINDINGS[@]} -gt 0 ]; then - echo "🔍 Performing forensics analysis..." - - # Perform git blame and commit analysis for each finding - FORENSICS_DATA=() - - for finding in "${FINDINGS[@]}"; do - TYPE=$(echo "$finding" | cut -d: -f1) - FILE=$(echo "$finding" | cut -d: -f2) - LINE=$(echo "$finding" | cut -d: -f3) - EXTRA=$(echo "$finding" | cut -d: -f4-) - - if [ -f "$FILE" ] && [ "$LINE" != "0" ]; then - echo "Analyzing: $FILE:$LINE" - - # Get commit that introduced this line - BLAME_OUTPUT=$(git blame -L "$LINE,$LINE" --porcelain "$FILE" 2>/dev/null || echo "") - - if [ -n "$BLAME_OUTPUT" ]; then - COMMIT_SHA=$(echo "$BLAME_OUTPUT" | grep "^[0-9a-f]\{40\}" | head -1 | cut -d' ' -f1) - AUTHOR=$(git log -1 --format="%an" "$COMMIT_SHA" 2>/dev/null || echo "Unknown") - COMMIT_DATE=$(git log -1 --format="%ai" "$COMMIT_SHA" 2>/dev/null || echo "Unknown") - COMMIT_MSG=$(git log -1 --format="%s" "$COMMIT_SHA" 2>/dev/null || echo "Unknown") - SHORT_SHA=$(echo "$COMMIT_SHA" | cut -c1-7) - - FORENSICS_DATA+=("$finding|$SHORT_SHA|$AUTHOR|$COMMIT_DATE|$COMMIT_MSG") - - echo " ✓ Found: commit $SHORT_SHA by $AUTHOR on $COMMIT_DATE" - else - FORENSICS_DATA+=("$finding|unknown||||") - echo " ⚠ Could not determine origin commit" - fi - else - FORENSICS_DATA+=("$finding|unknown||||") - fi - done - - echo "✅ Forensics analysis complete" -fi -``` +Pipe the FINDINGS array (one entry per line) to stdin of the `forensics-extractor` agent. +Collect its JSON-per-line stdout output into FORENSICS_DATA for Phase 7. ## Phase 7: Generate Agentic Fix Tasks Create actionable remediation tasks for each finding: -```bash -#!/bin/bash - -if [ ${#FINDINGS[@]} -gt 0 ]; then - echo "🛠️ Generating agentic fix tasks..." - - # Prepare detailed findings with forensics and fix tasks - FINDINGS_DETAILS="" - FIX_TASKS="" - - for i in "${!FORENSICS_DATA[@]}"; do - FORENSICS="${FORENSICS_DATA[$i]}" - - # Parse forensics data - FINDING=$(echo "$FORENSICS" | cut -d'|' -f1) - TYPE=$(echo "$FINDING" | cut -d: -f1) - FILE=$(echo "$FINDING" | cut -d: -f2) - LINE=$(echo "$FINDING" | cut -d: -f3) - EXTRA=$(echo "$FINDING" | cut -d: -f4-) - - COMMIT=$(echo "$FORENSICS" | cut -d'|' -f2) - AUTHOR=$(echo "$FORENSICS" | cut -d'|' -f3) - DATE=$(echo "$FORENSICS" | cut -d'|' -f4) - MSG=$(echo "$FORENSICS" | cut -d'|' -f5) - - # Generate finding details - FINDINGS_DETAILS+="### Finding $((i+1)): $TYPE\n\n" - FINDINGS_DETAILS+="**Location**: \`$FILE:$LINE\`\n\n" - - if [ -n "$EXTRA" ]; then - FINDINGS_DETAILS+="**Details**: $EXTRA\n\n" - fi - - # Add forensics information - FINDINGS_DETAILS+="**Forensics Analysis**:\n" - if [ "$COMMIT" != "unknown" ]; then - FINDINGS_DETAILS+="- Introduced in commit: \`$COMMIT\`\n" - FINDINGS_DETAILS+="- Author: $AUTHOR\n" - FINDINGS_DETAILS+="- Date: $DATE\n" - FINDINGS_DETAILS+="- Message: \"$MSG\"\n" - else - FINDINGS_DETAILS+="- Unable to determine origin commit (file may be new or line may have moved)\n" - fi - FINDINGS_DETAILS+="\n" - - # Generate fix task based on finding type - case "$TYPE" in - SECRET_EXFIL) - FIX_TASKS+="- [ ] **Task $((i+1))**: Review and remove secret exfiltration pattern in \`$FILE:$LINE\`\n" - FIX_TASKS+=" - Verify if external network call is legitimate\n" - FIX_TASKS+=" - If malicious: Remove the code and rotate any exposed credentials\n" - FIX_TASKS+=" - If legitimate: Add domain to approved network list and document why needed\n" - ;; - DYNAMIC_EXEC) - FIX_TASKS+="- [ ] **Task $((i+1))**: Audit dynamic code execution in \`$FILE:$LINE\`\n" - FIX_TASKS+=" - Replace eval/exec with safer alternatives\n" - FIX_TASKS+=" - Sanitize all user inputs before use\n" - FIX_TASKS+=" - Add input validation and consider allowlist approach\n" - ;; - OBFUSCATION) - FIX_TASKS+="- [ ] **Task $((i+1))**: Investigate obfuscated content in \`$FILE:$LINE\`\n" - FIX_TASKS+=" - Decode and review the obfuscated content\n" - FIX_TASKS+=" - If legitimate: Add comment explaining purpose\n" - FIX_TASKS+=" - If malicious: Remove and investigate how it was introduced\n" - ;; - DANGEROUS_OPS) - FIX_TASKS+="- [ ] **Task $((i+1))**: Secure dangerous file operations in \`$FILE:$LINE\`\n" - FIX_TASKS+=" - Validate all file paths before operations\n" - FIX_TASKS+=" - Use safe file operation alternatives\n" - FIX_TASKS+=" - Add explicit permission checks\n" - ;; - SUSPICIOUS_DOMAIN) - FIX_TASKS+="- [ ] **Task $((i+1))**: Verify network domain in \`$FILE:$LINE\`\n" - FIX_TASKS+=" - Check if domain is legitimate for this operation\n" - FIX_TASKS+=" - If suspicious: Remove and investigate origin\n" - FIX_TASKS+=" - Consider replacing with internal service\n" - ;; - SUSPICIOUS_KEYWORDS) - FIX_TASKS+="- [ ] **Task $((i+1))**: Review suspicious keywords in \`$FILE:$LINE\`\n" - FIX_TASKS+=" - Determine if code is intentionally malicious or just poorly named\n" - FIX_TASKS+=" - Remove if malicious, rename if legitimate\n" - FIX_TASKS+=" - Review commit history for context\n" - ;; - *) - FIX_TASKS+="- [ ] **Task $((i+1))**: Investigate $TYPE pattern in \`$FILE:$LINE\`\n" - FIX_TASKS+=" - Review the code and determine if it's a security risk\n" - FIX_TASKS+=" - Remediate if confirmed malicious\n" - FIX_TASKS+=" - Document findings and actions taken\n" - ;; - esac - FIX_TASKS+="\n" - done - - echo "✅ Generated ${#FINDINGS[@]} remediation tasks" -fi -``` +Pipe the FORENSICS_DATA JSON lines (from Phase 6) to stdin of the `fix-task-generator` agent. +Use its markdown stdout output as FIX_TASKS in Phase 8. ## Phase 8: Create Security Issues with Actionable Tasks @@ -840,3 +707,73 @@ A successful security scan: ## Begin Your Security Analysis Initialize your cache-memory, determine today's technique, and begin your comprehensive security scan of the `actions/setup/js` and `actions/setup/sh` directories! + +## agent: `forensics-extractor` +--- +model: claude-haiku-4.5 +description: Run git blame on each security finding and extract commit origin metadata as JSON +--- +You receive security findings as plain text, one per line, in the format: +`TYPE:FILE:LINE` or `TYPE:FILE:LINE:EXTRA` + +For each finding, run `git blame` to extract commit origin metadata and output a JSON object per line. + +```bash +#!/bin/bash +# Read findings from stdin, one per line +while IFS= read -r finding; do + [ -z "$finding" ] && continue + TYPE=$(echo "$finding" | cut -d: -f1) + FILE=$(echo "$finding" | cut -d: -f2) + LINE=$(echo "$finding" | cut -d: -f3) + + COMMIT="unknown" + AUTHOR="" + DATE="" + MSG="" + + if [ -f "$FILE" ] && [ "$LINE" != "0" ] && [ -n "$LINE" ]; then + BLAME_OUTPUT=$(git blame -L "$LINE,$LINE" --porcelain "$FILE" 2>/dev/null || echo "") + if [ -n "$BLAME_OUTPUT" ]; then + COMMIT_SHA=$(echo "$BLAME_OUTPUT" | grep "^[0-9a-f]\{40\}" | head -1 | cut -d' ' -f1) + if [ -n "$COMMIT_SHA" ] && [ "$COMMIT_SHA" != "0000000000000000000000000000000000000000" ]; then + AUTHOR=$(git log -1 --format="%an" "$COMMIT_SHA" 2>/dev/null || echo "Unknown") + DATE=$(git log -1 --format="%ai" "$COMMIT_SHA" 2>/dev/null || echo "Unknown") + MSG=$(git log -1 --format="%s" "$COMMIT_SHA" 2>/dev/null || echo "Unknown") + COMMIT=$(echo "$COMMIT_SHA" | cut -c1-7) + fi + fi + fi + + jq -cn --arg finding "$finding" --arg commit "$COMMIT" --arg author "$AUTHOR" --arg date "$DATE" --arg message "$MSG" \ + '{finding: $finding, commit: $commit, author: $author, date: $date, message: $message}' +done +``` + +Output one JSON object per line. No preamble, no summary. + +## agent: `fix-task-generator` +--- +model: claude-haiku-4.5 +description: Generate markdown remediation checklist from classified security findings +--- +You receive security finding records as JSON objects, one per line: +`{"finding":"TYPE:FILE:LINE","commit":"...","author":"...","date":"...","message":"..."}` + +For each record, produce one markdown checklist item based on TYPE: +- SECRET_EXFIL: review and remove exfiltration; verify call legitimacy; rotate exposed credentials +- DYNAMIC_EXEC: audit dynamic execution; replace eval/exec with safer alternatives; sanitize inputs +- OBFUSCATION: decode and investigate; if legitimate add a comment; if malicious remove and investigate +- DANGEROUS_OPS: validate all file paths; use safe operation alternatives; add permission checks +- SUSPICIOUS_DOMAIN: verify domain legitimacy; remove if suspicious; replace with internal service +- SUSPICIOUS_KEYWORDS: determine intent; remove if malicious or rename if legitimate; review history +- Other: investigate the pattern; review for security risk; remediate if malicious; document findings + +Format: +- [ ] **Task N**: [Action] in `FILE:LINE` + - [Step 1] + - [Step 2] + - [Step 3] + - Forensics: introduced in commit `COMMIT` by AUTHOR on DATE ("MESSAGE") [omit if commit == "unknown"] + +Output only markdown. No preamble, no code fences. diff --git a/pkg/workflow/observability_otlp_test.go b/pkg/workflow/observability_otlp_test.go index d007d7a7d8d..ab93bdfef94 100644 --- a/pkg/workflow/observability_otlp_test.go +++ b/pkg/workflow/observability_otlp_test.go @@ -795,7 +795,6 @@ func TestInjectOTLPConfig_MapHeaders(t *testing.T) { }) } - // correctly parsed by ParseFrontmatterConfig. func TestObservabilityConfigParsing_MapHeaders(t *testing.T) { t.Run("map headers parsed as any type", func(t *testing.T) {