1 line
77 KiB
JSON
1 line
77 KiB
JSON
{"repo": "alexpermiakov/sast-triage", "files": {".github/workflows/triage-seed.yml": {"8218a7ebd7f83e79c095ca7e9d6363bd3df1faf9": {"t": 1785746341.2871177, "n": 4849, "h": "0722a37e7ec36dad", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with — picks its endpoint and API key automatically\"\n type: choice\n required: true\n default: deepseek-v4-pro\n options:\n - deepseek-v4-flash\n - deepseek-v4-pro\n - kimi-k2.7-code\n - kimi-k3\n - claude-sonnet-5\n - claude-opus-4-8\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve endpoint and key\n id: llm\n # The model name is the only knob a maintainer turns; its API shape,\n # endpoint and secret follow from it here. Three fields that have to\n # agree is three chances to dispatch a run that points a DeepSeek key\n # at Anthropic's API and burns a full-backlog triage on a 401.\n #\n # The input reaches the shell as an env var and never as `${{ }}`\n # inside the script. GitHub expands `${{ }}` BEFORE bash sees the line,\n # so a model name of `x\"; rm -rf $HOME; #` would be executed rather\n # than printed. Same rule action.yml states for its own inputs.\n env:\n INPUT_MODEL: ${{ inputs.model }}\n DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}\n ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\n run: |\n set -euo pipefail\n # An empty base_url selects Claude's native API — the same inference\n # the binary does — so neither path is a default nobody chose.\n case \"$INPUT_MODEL\" in\n deepseek-*) provider=openai; base_url=https://api.deepseek.com/v1; secret=DEEPSEEK_API_KEY ;;\n kimi-*) provider=openai; base_url=https://api.moonshot.ai/v1; secret=MOONSHOT_API_KEY ;;\n claude-*) provider=anthropic; base_url=; secret=ANTHROPIC_API_KEY ;;\n *) echo \"::error::unknown model '$INPUT_MODEL' — add its endpoint and secret to this case first\"; exit 1 ;;\n esac\n # Fail here, not forty findings into the run: a missing secret is a\n # repo-settings fix, and naming it saves the round trip.\n if [ -z \"${!secret}\" ]; then\n echo \"::error::$INPUT_MODEL needs the $secret secret, which is not set on this repository\"\n exit 1\n fi\n {\n echo \"provider=$provider\"\n echo \"base_url=$base_url\"\n echo \"secret=$secret\"\n } >> \"$GITHUB_OUTPUT\"\n echo \"seeding with $INPUT_MODEL via $provider${base_url:+ at $base_url}\"\n\n - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n\n - name: Scan\n run: |\n pipx install semgrep\n semgrep scan --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: ${{ steps.llm.outputs.provider }}\n model: ${{ inputs.model }}\n base-url: ${{ steps.llm.outputs.base_url }}\n # Indexed, not named: the secret follows the chosen model, and the\n # resolve step has already failed the run if it is unset.\n api-key: ${{ secrets[steps.llm.outputs.secret] }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n triage-summary.md\n retention-days: 7\n\n"}, "7af13e991836689b96206369cfa7a031541718f4": {"t": 1785762218.6413138, "n": 4849, "h": "0722a37e7ec36dad", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with — picks its endpoint and API key automatically\"\n type: choice\n required: true\n default: deepseek-v4-pro\n options:\n - deepseek-v4-flash\n - deepseek-v4-pro\n - kimi-k2.7-code\n - kimi-k3\n - claude-sonnet-5\n - claude-opus-4-8\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve endpoint and key\n id: llm\n # The model name is the only knob a maintainer turns; its API shape,\n # endpoint and secret follow from it here. Three fields that have to\n # agree is three chances to dispatch a run that points a DeepSeek key\n # at Anthropic's API and burns a full-backlog triage on a 401.\n #\n # The input reaches the shell as an env var and never as `${{ }}`\n # inside the script. GitHub expands `${{ }}` BEFORE bash sees the line,\n # so a model name of `x\"; rm -rf $HOME; #` would be executed rather\n # than printed. Same rule action.yml states for its own inputs.\n env:\n INPUT_MODEL: ${{ inputs.model }}\n DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}\n ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\n run: |\n set -euo pipefail\n # An empty base_url selects Claude's native API — the same inference\n # the binary does — so neither path is a default nobody chose.\n case \"$INPUT_MODEL\" in\n deepseek-*) provider=openai; base_url=https://api.deepseek.com/v1; secret=DEEPSEEK_API_KEY ;;\n kimi-*) provider=openai; base_url=https://api.moonshot.ai/v1; secret=MOONSHOT_API_KEY ;;\n claude-*) provider=anthropic; base_url=; secret=ANTHROPIC_API_KEY ;;\n *) echo \"::error::unknown model '$INPUT_MODEL' — add its endpoint and secret to this case first\"; exit 1 ;;\n esac\n # Fail here, not forty findings into the run: a missing secret is a\n # repo-settings fix, and naming it saves the round trip.\n if [ -z \"${!secret}\" ]; then\n echo \"::error::$INPUT_MODEL needs the $secret secret, which is not set on this repository\"\n exit 1\n fi\n {\n echo \"provider=$provider\"\n echo \"base_url=$base_url\"\n echo \"secret=$secret\"\n } >> \"$GITHUB_OUTPUT\"\n echo \"seeding with $INPUT_MODEL via $provider${base_url:+ at $base_url}\"\n\n - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n\n - name: Scan\n run: |\n pipx install semgrep\n semgrep scan --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: ${{ steps.llm.outputs.provider }}\n model: ${{ inputs.model }}\n base-url: ${{ steps.llm.outputs.base_url }}\n # Indexed, not named: the secret follows the chosen model, and the\n # resolve step has already failed the run if it is unset.\n api-key: ${{ secrets[steps.llm.outputs.secret] }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n triage-summary.md\n retention-days: 7\n\n"}, "40b3d47f3483fb8a1d2f53f2b4f111e9cfc36ab3": {"t": 1785762220.6450915, "n": 4849, "h": "91bfd1d167ff9eb3", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with — picks its endpoint and API key automatically\"\n type: choice\n required: true\n default: deepseek-v4-pro\n options:\n - deepseek-v4-flash\n - deepseek-v4-pro\n - kimi-k2.7-code\n - kimi-k3\n - claude-sonnet-5\n - claude-opus-4-8\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve endpoint and key\n id: llm\n # The model name is the only knob a maintainer turns; its API shape,\n # endpoint and secret follow from it here. Three fields that have to\n # agree is three chances to dispatch a run that points a DeepSeek key\n # at Anthropic's API and burns a full-backlog triage on a 401.\n #\n # The input reaches the shell as an env var and never as `${{ }}`\n # inside the script. GitHub expands `${{ }}` BEFORE bash sees the line,\n # so a model name of `x\"; rm -rf $HOME; #` would be executed rather\n # than printed. Same rule action.yml states for its own inputs.\n env:\n INPUT_MODEL: ${{ inputs.model }}\n DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}\n ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\n run: |\n set -euo pipefail\n # An empty base_url selects Claude's native API — the same inference\n # the binary does — so neither path is a default nobody chose.\n case \"$INPUT_MODEL\" in\n deepseek-*) provider=openai; base_url=https://api.deepseek.com/v1; secret=DEEPSEEK_API_KEY ;;\n kimi-*) provider=openai; base_url=https://api.moonshot.ai/v1; secret=MOONSHOT_API_KEY ;;\n claude-*) provider=anthropic; base_url=; secret=ANTHROPIC_API_KEY ;;\n *) echo \"::error::unknown model '$INPUT_MODEL' — add its endpoint and secret to this case first\"; exit 1 ;;\n esac\n # Fail here, not forty findings into the run: a missing secret is a\n # repo-settings fix, and naming it saves the round trip.\n if [ -z \"${!secret}\" ]; then\n echo \"::error::$INPUT_MODEL needs the $secret secret, which is not set on this repository\"\n exit 1\n fi\n {\n echo \"provider=$provider\"\n echo \"base_url=$base_url\"\n echo \"secret=$secret\"\n } >> \"$GITHUB_OUTPUT\"\n echo \"seeding with $INPUT_MODEL via $provider${base_url:+ at $base_url}\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n\n - name: Scan\n run: |\n pipx install semgrep\n semgrep scan --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: ${{ steps.llm.outputs.provider }}\n model: ${{ inputs.model }}\n base-url: ${{ steps.llm.outputs.base_url }}\n # Indexed, not named: the secret follows the chosen model, and the\n # resolve step has already failed the run if it is unset.\n api-key: ${{ secrets[steps.llm.outputs.secret] }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n triage-summary.md\n retention-days: 7\n\n"}, "859b0859092ee5244d1a0dfdfb964c9a07ba2eac": {"t": 1785762222.7609956, "n": 4849, "h": "75ea5936ec45e82e", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with — picks its endpoint and API key automatically\"\n type: choice\n required: true\n default: deepseek-v4-pro\n options:\n - deepseek-v4-flash\n - deepseek-v4-pro\n - kimi-k3\n - kimi-k2.7-code\n - claude-opus-4-8\n - claude-sonnet-5\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve endpoint and key\n id: llm\n # The model name is the only knob a maintainer turns; its API shape,\n # endpoint and secret follow from it here. Three fields that have to\n # agree is three chances to dispatch a run that points a DeepSeek key\n # at Anthropic's API and burns a full-backlog triage on a 401.\n #\n # The input reaches the shell as an env var and never as `${{ }}`\n # inside the script. GitHub expands `${{ }}` BEFORE bash sees the line,\n # so a model name of `x\"; rm -rf $HOME; #` would be executed rather\n # than printed. Same rule action.yml states for its own inputs.\n env:\n INPUT_MODEL: ${{ inputs.model }}\n DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}\n ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}\n run: |\n set -euo pipefail\n # An empty base_url selects Claude's native API — the same inference\n # the binary does — so neither path is a default nobody chose.\n case \"$INPUT_MODEL\" in\n deepseek-*) provider=openai; base_url=https://api.deepseek.com/v1; secret=DEEPSEEK_API_KEY ;;\n kimi-*) provider=openai; base_url=https://api.moonshot.ai/v1; secret=MOONSHOT_API_KEY ;;\n claude-*) provider=anthropic; base_url=; secret=ANTHROPIC_API_KEY ;;\n *) echo \"::error::unknown model '$INPUT_MODEL' — add its endpoint and secret to this case first\"; exit 1 ;;\n esac\n # Fail here, not forty findings into the run: a missing secret is a\n # repo-settings fix, and naming it saves the round trip.\n if [ -z \"${!secret}\" ]; then\n echo \"::error::$INPUT_MODEL needs the $secret secret, which is not set on this repository\"\n exit 1\n fi\n {\n echo \"provider=$provider\"\n echo \"base_url=$base_url\"\n echo \"secret=$secret\"\n } >> \"$GITHUB_OUTPUT\"\n echo \"seeding with $INPUT_MODEL via $provider${base_url:+ at $base_url}\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n\n - name: Scan\n run: |\n pipx install semgrep\n semgrep scan --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: ${{ steps.llm.outputs.provider }}\n model: ${{ inputs.model }}\n base-url: ${{ steps.llm.outputs.base_url }}\n # Indexed, not named: the secret follows the chosen model, and the\n # resolve step has already failed the run if it is unset.\n api-key: ${{ secrets[steps.llm.outputs.secret] }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n triage-summary.md\n retention-days: 7\n\n"}, "bccbff86a92eb1f8dbda3888e0912c5c7ada024d": {"t": 1785762228.4933734, "n": 3349, "h": "4bad81e0301f0653", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with, e.g. deepseek-v4-flash or claude-sonnet-5\"\n type: string\n required: true\n default: deepseek-v4-flash\n base_url:\n description: \"OpenAI-compatible endpoint — clear it to use Claude's native API instead\"\n type: string\n required: false\n default: https://api.deepseek.com/v1\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve provider\n id: llm\n # Both dispatch inputs reach the shell as env vars and never as\n # `${{ }}` inside the script. GitHub expands `${{ }}` BEFORE bash sees\n # the line, so a model name of `x\"; rm -rf $HOME; #` would be executed\n # rather than printed. Same rule action.yml states for its own inputs.\n env:\n INPUT_BASE_URL: ${{ inputs.base_url }}\n INPUT_MODEL: ${{ inputs.model }}\n run: |\n # base-url alone selects the OpenAI-compatible path; empty means\n # Claude's native API. Same inference the binary does, so neither is\n # a default nobody chose.\n if [ -n \"$INPUT_BASE_URL\" ]; then PROVIDER=openai; else PROVIDER=anthropic; fi\n echo \"provider=$PROVIDER\" >> \"$GITHUB_OUTPUT\"\n echo \"seeding with $INPUT_MODEL via $PROVIDER\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n\n - name: Scan\n run: |\n pipx install semgrep\n semgrep scan --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: anthropic\n model: claude-opus-4-8\n api-key: ${{ secrets.ANTHROPIC_API_KEY }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n triage-summary.md\n retention-days: 7\n\n"}, "108a4b0a0bf1b7d68b6903bf3d1ba4d35a88ddef": {"t": 1785762230.8017619, "n": 3372, "h": "e8fc23fe100e6a71", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with, e.g. deepseek-v4-flash or claude-sonnet-5\"\n type: string\n required: true\n default: deepseek-v4-flash\n base_url:\n description: \"OpenAI-compatible endpoint — clear it to use Claude's native API instead\"\n type: string\n required: false\n default: https://api.deepseek.com/v1\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve provider\n id: llm\n # Both dispatch inputs reach the shell as env vars and never as\n # `${{ }}` inside the script. GitHub expands `${{ }}` BEFORE bash sees\n # the line, so a model name of `x\"; rm -rf $HOME; #` would be executed\n # rather than printed. Same rule action.yml states for its own inputs.\n env:\n INPUT_BASE_URL: ${{ inputs.base_url }}\n INPUT_MODEL: ${{ inputs.model }}\n run: |\n # base-url alone selects the OpenAI-compatible path; empty means\n # Claude's native API. Same inference the binary does, so neither is\n # a default nobody chose.\n if [ -n \"$INPUT_BASE_URL\" ]; then PROVIDER=openai; else PROVIDER=anthropic; fi\n echo \"provider=$PROVIDER\" >> \"$GITHUB_OUTPUT\"\n echo \"seeding with $INPUT_MODEL via $PROVIDER\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n\n - name: Scan\n run: |\n pipx install semgrep\n semgrep scan --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: anthropic\n model: claude-opus-4-8\n base-url: \"\"\n api-key: ${{ secrets.ANTHROPIC_API_KEY }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n triage-summary.md\n retention-days: 7\n\n"}, "bb4fac67cf4a97be4ab9cad597b7fac614c881f7": {"t": 1785762233.9079986, "n": 3371, "h": "ed787c40c95d3611", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with, e.g. deepseek-v4-flash or claude-sonnet-5\"\n type: string\n required: true\n default: deepseek-v4-flash\n base_url:\n description: \"OpenAI-compatible endpoint — clear it to use Claude's native API instead\"\n type: string\n required: false\n default: https://api.deepseek.com/v1\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve provider\n id: llm\n # Both dispatch inputs reach the shell as env vars and never as\n # `${{ }}` inside the script. GitHub expands `${{ }}` BEFORE bash sees\n # the line, so a model name of `x\"; rm -rf $HOME; #` would be executed\n # rather than printed. Same rule action.yml states for its own inputs.\n env:\n INPUT_BASE_URL: ${{ inputs.base_url }}\n INPUT_MODEL: ${{ inputs.model }}\n run: |\n # base-url alone selects the OpenAI-compatible path; empty means\n # Claude's native API. Same inference the binary does, so neither is\n # a default nobody chose.\n if [ -n \"$INPUT_BASE_URL\" ]; then PROVIDER=openai; else PROVIDER=anthropic; fi\n echo \"provider=$PROVIDER\" >> \"$GITHUB_OUTPUT\"\n echo \"seeding with $INPUT_MODEL via $PROVIDER\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n\n - name: Scan\n run: |\n pipx install semgrep\n semgrep scan --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: antropic\n model: claude-opus-4-8\n base-url: \"\"\n api-key: ${{ secrets.ANTHROPIC_API_KEY }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n triage-summary.md\n retention-days: 7\n\n"}, "012da2a930ecee3f66f1c97fc17902230dabf610": {"t": 1785762236.0004961, "n": 3378, "h": "55953d8d647497ce", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with, e.g. deepseek-v4-flash or claude-sonnet-5\"\n type: string\n required: true\n default: deepseek-v4-flash\n base_url:\n description: \"OpenAI-compatible endpoint — clear it to use Claude's native API instead\"\n type: string\n required: false\n default: https://api.deepseek.com/v1\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve provider\n id: llm\n # Both dispatch inputs reach the shell as env vars and never as\n # `${{ }}` inside the script. GitHub expands `${{ }}` BEFORE bash sees\n # the line, so a model name of `x\"; rm -rf $HOME; #` would be executed\n # rather than printed. Same rule action.yml states for its own inputs.\n env:\n INPUT_BASE_URL: ${{ inputs.base_url }}\n INPUT_MODEL: ${{ inputs.model }}\n run: |\n # base-url alone selects the OpenAI-compatible path; empty means\n # Claude's native API. Same inference the binary does, so neither is\n # a default nobody chose.\n if [ -n \"$INPUT_BASE_URL\" ]; then PROVIDER=openai; else PROVIDER=anthropic; fi\n echo \"provider=$PROVIDER\" >> \"$GITHUB_OUTPUT\"\n echo \"seeding with $INPUT_MODEL via $PROVIDER\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n\n - name: Scan\n run: |\n pipx install semgrep\n semgrep scan --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: antropic\n model: claude-opus-4-8\n base-url: \"\"\n openai-api-key: ${{ secrets.ANTHROPIC_API_KEY }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n triage-summary.md\n retention-days: 7\n\n"}, "b62609725770b4267a8f9267d1a8634a1d3bdad6": {"t": 1785762238.3934484, "n": 3377, "h": "d41cfdf9c88f62d5", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with, e.g. deepseek-v4-flash or claude-sonnet-5\"\n type: string\n required: true\n default: deepseek-v4-flash\n base_url:\n description: \"OpenAI-compatible endpoint — clear it to use Claude's native API instead\"\n type: string\n required: false\n default: https://api.deepseek.com/v1\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve provider\n id: llm\n # Both dispatch inputs reach the shell as env vars and never as\n # `${{ }}` inside the script. GitHub expands `${{ }}` BEFORE bash sees\n # the line, so a model name of `x\"; rm -rf $HOME; #` would be executed\n # rather than printed. Same rule action.yml states for its own inputs.\n env:\n INPUT_BASE_URL: ${{ inputs.base_url }}\n INPUT_MODEL: ${{ inputs.model }}\n run: |\n # base-url alone selects the OpenAI-compatible path; empty means\n # Claude's native API. Same inference the binary does, so neither is\n # a default nobody chose.\n if [ -n \"$INPUT_BASE_URL\" ]; then PROVIDER=openai; else PROVIDER=anthropic; fi\n echo \"provider=$PROVIDER\" >> \"$GITHUB_OUTPUT\"\n echo \"seeding with $INPUT_MODEL via $PROVIDER\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n\n - name: Scan\n run: |\n pipx install semgrep\n semgrep scan --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: antropic\n model: claude-opus-4-8\n base-url: \"\"\n openai-api-key: ${{ secrets.DEEPSEEK_API_KEY }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n triage-summary.md\n retention-days: 7\n\n"}, "47300403b1a444e92c5494837f8b7bdab8cbf450": {"t": 1785762240.3911364, "n": 3426, "h": "a97029b58f6c8777", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with, e.g. deepseek-v4-flash or claude-sonnet-5\"\n type: string\n required: true\n default: deepseek-v4-flash\n base_url:\n description: \"OpenAI-compatible endpoint — clear it to use Claude's native API instead\"\n type: string\n required: false\n default: https://api.deepseek.com/v1\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve provider\n id: llm\n # Both dispatch inputs reach the shell as env vars and never as\n # `${{ }}` inside the script. GitHub expands `${{ }}` BEFORE bash sees\n # the line, so a model name of `x\"; rm -rf $HOME; #` would be executed\n # rather than printed. Same rule action.yml states for its own inputs.\n env:\n INPUT_BASE_URL: ${{ inputs.base_url }}\n INPUT_MODEL: ${{ inputs.model }}\n run: |\n # base-url alone selects the OpenAI-compatible path; empty means\n # Claude's native API. Same inference the binary does, so neither is\n # a default nobody chose.\n if [ -n \"$INPUT_BASE_URL\" ]; then PROVIDER=openai; else PROVIDER=anthropic; fi\n echo \"provider=$PROVIDER\" >> \"$GITHUB_OUTPUT\"\n echo \"seeding with $INPUT_MODEL via $PROVIDER\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n\n - name: Scan\n run: |\n pipx install semgrep\n semgrep scan --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: ${{ steps.llm.outputs.provider }}\n model: ${{ inputs.model }}\n base-url: ${{ inputs.base_url }}\n openai-api-key: ${{ secrets.DEEPSEEK_API_KEY }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n triage-summary.md\n retention-days: 7\n\n"}, "82ff62fd59f0e727b42d452be1e9de79720521ac": {"t": 1785762253.5932963, "n": 3474, "h": "7e52d3ff0c4de50d", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with, e.g. deepseek-v4-flash or claude-sonnet-5\"\n type: string\n required: true\n default: deepseek-v4-flash\n base_url:\n description: \"OpenAI-compatible endpoint — clear it to use Claude's native API instead\"\n type: string\n required: false\n default: https://api.deepseek.com/v1\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve provider\n id: llm\n # Both dispatch inputs reach the shell as env vars and never as\n # `${{ }}` inside the script. GitHub expands `${{ }}` BEFORE bash sees\n # the line, so a model name of `x\"; rm -rf $HOME; #` would be executed\n # rather than printed. Same rule action.yml states for its own inputs.\n env:\n INPUT_BASE_URL: ${{ inputs.base_url }}\n INPUT_MODEL: ${{ inputs.model }}\n run: |\n # base-url alone selects the OpenAI-compatible path; empty means\n # Claude's native API. Same inference the binary does, so neither is\n # a default nobody chose.\n if [ -n \"$INPUT_BASE_URL\" ]; then PROVIDER=openai; else PROVIDER=anthropic; fi\n echo \"provider=$PROVIDER\" >> \"$GITHUB_OUTPUT\"\n echo \"seeding with $INPUT_MODEL via $PROVIDER\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n\n - name: Scan\n run: |\n pipx install opengrep\n opengrep scan --config=auto --dataflow-traces \\\n --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: ${{ steps.llm.outputs.provider }}\n model: ${{ inputs.model }}\n base-url: ${{ inputs.base_url }}\n openai-api-key: ${{ secrets.DEEPSEEK_API_KEY }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n triage-summary.md\n retention-days: 7\n\n"}, "8e9b21ca3d0a14ab615663ba54c8e98568800c7d": {"t": 1785762254.8372428, "n": 3472, "h": "faa8cf40267d930d", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with, e.g. deepseek-v4-flash or claude-sonnet-5\"\n type: string\n required: true\n default: deepseek-v4-flash\n base_url:\n description: \"OpenAI-compatible endpoint — clear it to use Claude's native API instead\"\n type: string\n required: false\n default: https://api.deepseek.com/v1\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve provider\n id: llm\n # Both dispatch inputs reach the shell as env vars and never as\n # `${{ }}` inside the script. GitHub expands `${{ }}` BEFORE bash sees\n # the line, so a model name of `x\"; rm -rf $HOME; #` would be executed\n # rather than printed. Same rule action.yml states for its own inputs.\n env:\n INPUT_BASE_URL: ${{ inputs.base_url }}\n INPUT_MODEL: ${{ inputs.model }}\n run: |\n # base-url alone selects the OpenAI-compatible path; empty means\n # Claude's native API. Same inference the binary does, so neither is\n # a default nobody chose.\n if [ -n \"$INPUT_BASE_URL\" ]; then PROVIDER=openai; else PROVIDER=anthropic; fi\n echo \"provider=$PROVIDER\" >> \"$GITHUB_OUTPUT\"\n echo \"seeding with $INPUT_MODEL via $PROVIDER\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n\n - name: Scan\n run: |\n pipx install semgrep\n semgrep scan --config=auto --dataflow-traces \\\n --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: ${{ steps.llm.outputs.provider }}\n model: ${{ inputs.model }}\n base-url: ${{ inputs.base_url }}\n openai-api-key: ${{ secrets.DEEPSEEK_API_KEY }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n triage-summary.md\n retention-days: 7\n\n"}, "86866e08c2fb4d48e557425d003dfeb600c6ae1a": {"t": 1785762278.343426, "n": 3097, "h": "2925683814e87dfa", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with, e.g. deepseek-v4-flash or claude-sonnet-5\"\n type: string\n required: true\n default: deepseek-v4-flash\n base_url:\n description: \"OpenAI-compatible endpoint — clear it to use Claude's native API instead\"\n type: string\n required: false\n default: https://api.deepseek.com/v1\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve provider\n id: llm\n env:\n INPUT_BASE_URL: ${{ inputs.base_url }}\n run: |\n # base-url alone selects the OpenAI-compatible path; empty means\n # Claude's native API. Same inference the binary does, so neither is\n # a default nobody chose.\n if [ -n \"$INPUT_BASE_URL\" ]; then PROVIDER=openai; else PROVIDER=anthropic; fi\n echo \"provider=$PROVIDER\" >> \"$GITHUB_OUTPUT\"\n echo \"seeding with ${{ inputs.model }} via $PROVIDER\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n\n - name: Scan\n run: |\n pipx install semgrep\n semgrep scan --config=auto --dataflow-traces \\\n --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: ${{ steps.llm.outputs.provider }}\n model: ${{ inputs.model }}\n base-url: ${{ inputs.base_url }}\n openai-api-key: ${{ secrets.DEEPSEEK_API_KEY }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n retention-days: 7\n\n"}, "9e7f5f004322940f8951544f93c5def6438acfa6": {"t": 1785762280.6169782, "n": 3092, "h": "56f897b540bb73f9", "c": "# Seeding: a deliberate one-off a maintainer runs by hand. It triages the whole\n# backlog and opens ONE pull request titled \"seed sast-triage cache\".\n#\n# That PR is the point of this workflow. It is where a human reviews the entire\n# proposed suppression set at once, with cited evidence per verdict, and either\n# accepts it or edits it. Everything after is incremental: feature PRs add a\n# handful of cache lines and nobody has to think about them again.\n#\n# Re-run it after anything that shifts fingerprints wholesale — a scanner\n# upgrade, a ruleset change, a large refactor.\nname: Triage (seed)\n\non:\n workflow_dispatch:\n inputs:\n model:\n description: \"Model to triage with, e.g. DeepSeek-V4-Pro or claude-sonnet-5\"\n type: string\n required: true\n default: DeepSeek-V4-Pro\n base_url:\n description: \"OpenAI-compatible endpoint — clear it to use Claude's native API instead\"\n type: string\n required: false\n default: https://api.deepseek.com/v1\n\nconcurrency:\n group: triage-seed\n cancel-in-progress: false\n\npermissions:\n contents: write # push the seed branch\n pull-requests: write # open the seed PR\n\njobs:\n seed:\n runs-on: ubuntu-latest\n steps:\n - name: Resolve provider\n id: llm\n env:\n INPUT_BASE_URL: ${{ inputs.base_url }}\n run: |\n # base-url alone selects the OpenAI-compatible path; empty means\n # Claude's native API. Same inference the binary does, so neither is\n # a default nobody chose.\n if [ -n \"$INPUT_BASE_URL\" ]; then PROVIDER=openai; else PROVIDER=anthropic; fi\n echo \"provider=$PROVIDER\" >> \"$GITHUB_OUTPUT\"\n echo \"seeding with ${{ inputs.model }} via $PROVIDER\"\n\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n\n - name: Scan\n run: |\n pipx install semgrep\n semgrep scan --config=auto --dataflow-traces \\\n --exclude testdata --sarif-output=findings.sarif\n\n # mode: baseline — triage everything, gate nothing. A seeding run that\n # fails on the backlog it was invoked to catalogue is useless.\n - name: Triage\n uses: ./\n with:\n provider: ${{ steps.llm.outputs.provider }}\n model: ${{ inputs.model }}\n base-url: ${{ inputs.base_url }}\n openai-api-key: ${{ secrets.DEEPSEEK_API_KEY }}\n scope: full\n mode: baseline\n cache-write: pr\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.sha }}\n\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-seed-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n retention-days: 7\n"}}, ".github/workflows/triage-pr.yml": {"8218a7ebd7f83e79c095ca7e9d6363bd3df1faf9": {"t": 1785747050.6107812, "n": 3427, "h": "76b3c7f0cc461082", "c": "# Pull requests: diff scope, enforcing gate, cache committed to the PR's own\n# head branch. One of three triage workflows, split by trigger so each one's\n# scope and gating are readable without cross-referencing the others.\nname: Triage (PR)\n\non:\n pull_request:\n branches: [main]\n\nconcurrency:\n group: triage-pr-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write # commit the cache delta to the PR head branch\n pull-requests: write # inline review comments\n\njobs:\n triage:\n runs-on: ubuntu-latest\n steps:\n # The PR head SHA, not the merge commit: the cache delta is committed on\n # top of it and pushed to the head branch, which has to be a fast-forward.\n # fetch-depth 0 so origin/<base> exists for the diff.\n - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n with:\n fetch-depth: 0\n ref: ${{ github.event.pull_request.head.sha }}\n\n - name: Check for an API key\n id: llm\n env:\n OPENAI_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n run: |\n # Fork PRs get no secrets. Triage is skipped rather than failed: a\n # contributor cannot fix a missing secret, and a red X they cannot\n # act on teaches everyone to ignore this check.\n if [ -z \"$OPENAI_API_KEY\" ]; then\n echo \"skip=true\" >> \"$GITHUB_OUTPUT\"\n echo \"::notice::no API key (fork PR?) — triage skipped.\"\n else\n echo \"skip=false\" >> \"$GITHUB_OUTPUT\"\n fi\n\n # testdata stays excluded: those fixtures' line numbers are pinned to unit\n # tests, and the agent short-circuits them to benign anyway.\n - name: Scan\n if: steps.llm.outputs.skip != 'true'\n run: |\n pipx install semgrep\n semgrep scan --exclude testdata --sarif-output=findings.sarif\n\n # Dogfood: `uses: ./` resolves action.yml from this checkout, so PRs run\n # their own action code.\n - name: Triage\n if: steps.llm.outputs.skip != 'true'\n uses: ./\n with:\n provider: openai\n model: deepseek-v4-flash\n base-url: https://api.deepseek.com/v1\n openai-api-key: ${{ secrets.DEEPSEEK_API_KEY }}\n scope: diff\n base-ref: origin/${{ github.base_ref }}\n mode: enforce\n cache-write: branch\n pr-comments: \"true\"\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.event.pull_request.head.sha }}\n\n # The digest, not the report: the step summary caps at 1 MiB, and a large\n # backlog blows past that. The complete report rides in the artifact.\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n # Emitted on every run, gate or no gate: the annotated SARIF relabels\n # benign findings via suppressions[] and deletes nothing, so it is the\n # integration surface for DefectDojo and friends.\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n .sast-triage/cache.json\n retention-days: 7\n\n"}, "7af13e991836689b96206369cfa7a031541718f4": {"t": 1785762225.0014796, "n": 3427, "h": "76b3c7f0cc461082", "c": "# Pull requests: diff scope, enforcing gate, cache committed to the PR's own\n# head branch. One of three triage workflows, split by trigger so each one's\n# scope and gating are readable without cross-referencing the others.\nname: Triage (PR)\n\non:\n pull_request:\n branches: [main]\n\nconcurrency:\n group: triage-pr-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write # commit the cache delta to the PR head branch\n pull-requests: write # inline review comments\n\njobs:\n triage:\n runs-on: ubuntu-latest\n steps:\n # The PR head SHA, not the merge commit: the cache delta is committed on\n # top of it and pushed to the head branch, which has to be a fast-forward.\n # fetch-depth 0 so origin/<base> exists for the diff.\n - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1\n with:\n fetch-depth: 0\n ref: ${{ github.event.pull_request.head.sha }}\n\n - name: Check for an API key\n id: llm\n env:\n OPENAI_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n run: |\n # Fork PRs get no secrets. Triage is skipped rather than failed: a\n # contributor cannot fix a missing secret, and a red X they cannot\n # act on teaches everyone to ignore this check.\n if [ -z \"$OPENAI_API_KEY\" ]; then\n echo \"skip=true\" >> \"$GITHUB_OUTPUT\"\n echo \"::notice::no API key (fork PR?) — triage skipped.\"\n else\n echo \"skip=false\" >> \"$GITHUB_OUTPUT\"\n fi\n\n # testdata stays excluded: those fixtures' line numbers are pinned to unit\n # tests, and the agent short-circuits them to benign anyway.\n - name: Scan\n if: steps.llm.outputs.skip != 'true'\n run: |\n pipx install semgrep\n semgrep scan --exclude testdata --sarif-output=findings.sarif\n\n # Dogfood: `uses: ./` resolves action.yml from this checkout, so PRs run\n # their own action code.\n - name: Triage\n if: steps.llm.outputs.skip != 'true'\n uses: ./\n with:\n provider: openai\n model: deepseek-v4-flash\n base-url: https://api.deepseek.com/v1\n openai-api-key: ${{ secrets.DEEPSEEK_API_KEY }}\n scope: diff\n base-ref: origin/${{ github.base_ref }}\n mode: enforce\n cache-write: branch\n pr-comments: \"true\"\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.event.pull_request.head.sha }}\n\n # The digest, not the report: the step summary caps at 1 MiB, and a large\n # backlog blows past that. The complete report rides in the artifact.\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n # Emitted on every run, gate or no gate: the annotated SARIF relabels\n # benign findings via suppressions[] and deletes nothing, so it is the\n # integration surface for DefectDojo and friends.\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n .sast-triage/cache.json\n retention-days: 7\n\n"}, "47300403b1a444e92c5494837f8b7bdab8cbf450": {"t": 1785762226.349538, "n": 3427, "h": "27d4edd08c77dec3", "c": "# Pull requests: diff scope, enforcing gate, cache committed to the PR's own\n# head branch. One of three triage workflows, split by trigger so each one's\n# scope and gating are readable without cross-referencing the others.\nname: Triage (PR)\n\non:\n pull_request:\n branches: [main]\n\nconcurrency:\n group: triage-pr-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write # commit the cache delta to the PR head branch\n pull-requests: write # inline review comments\n\njobs:\n triage:\n runs-on: ubuntu-latest\n steps:\n # The PR head SHA, not the merge commit: the cache delta is committed on\n # top of it and pushed to the head branch, which has to be a fast-forward.\n # fetch-depth 0 so origin/<base> exists for the diff.\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n ref: ${{ github.event.pull_request.head.sha }}\n\n - name: Check for an API key\n id: llm\n env:\n OPENAI_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n run: |\n # Fork PRs get no secrets. Triage is skipped rather than failed: a\n # contributor cannot fix a missing secret, and a red X they cannot\n # act on teaches everyone to ignore this check.\n if [ -z \"$OPENAI_API_KEY\" ]; then\n echo \"skip=true\" >> \"$GITHUB_OUTPUT\"\n echo \"::notice::no API key (fork PR?) — triage skipped.\"\n else\n echo \"skip=false\" >> \"$GITHUB_OUTPUT\"\n fi\n\n # testdata stays excluded: those fixtures' line numbers are pinned to unit\n # tests, and the agent short-circuits them to benign anyway.\n - name: Scan\n if: steps.llm.outputs.skip != 'true'\n run: |\n pipx install semgrep\n semgrep scan --exclude testdata --sarif-output=findings.sarif\n\n # Dogfood: `uses: ./` resolves action.yml from this checkout, so PRs run\n # their own action code.\n - name: Triage\n if: steps.llm.outputs.skip != 'true'\n uses: ./\n with:\n provider: openai\n model: deepseek-v4-flash\n base-url: https://api.deepseek.com/v1\n openai-api-key: ${{ secrets.DEEPSEEK_API_KEY }}\n scope: diff\n base-ref: origin/${{ github.base_ref }}\n mode: enforce\n cache-write: branch\n pr-comments: \"true\"\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.event.pull_request.head.sha }}\n\n # The digest, not the report: the step summary caps at 1 MiB, and a large\n # backlog blows past that. The complete report rides in the artifact.\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n # Emitted on every run, gate or no gate: the annotated SARIF relabels\n # benign findings via suppressions[] and deletes nothing, so it is the\n # integration surface for DefectDojo and friends.\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n .sast-triage/cache.json\n retention-days: 7\n\n"}, "82ff62fd59f0e727b42d452be1e9de79720521ac": {"t": 1785762227.915212, "n": 3475, "h": "a6742d91e6a72879", "c": "# Pull requests: diff scope, enforcing gate, cache committed to the PR's own\n# head branch. One of three triage workflows, split by trigger so each one's\n# scope and gating are readable without cross-referencing the others.\nname: Triage (PR)\n\non:\n pull_request:\n branches: [main]\n\nconcurrency:\n group: triage-pr-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write # commit the cache delta to the PR head branch\n pull-requests: write # inline review comments\n\njobs:\n triage:\n runs-on: ubuntu-latest\n steps:\n # The PR head SHA, not the merge commit: the cache delta is committed on\n # top of it and pushed to the head branch, which has to be a fast-forward.\n # fetch-depth 0 so origin/<base> exists for the diff.\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n ref: ${{ github.event.pull_request.head.sha }}\n\n - name: Check for an API key\n id: llm\n env:\n OPENAI_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n run: |\n # Fork PRs get no secrets. Triage is skipped rather than failed: a\n # contributor cannot fix a missing secret, and a red X they cannot\n # act on teaches everyone to ignore this check.\n if [ -z \"$OPENAI_API_KEY\" ]; then\n echo \"skip=true\" >> \"$GITHUB_OUTPUT\"\n echo \"::notice::no API key (fork PR?) — triage skipped.\"\n else\n echo \"skip=false\" >> \"$GITHUB_OUTPUT\"\n fi\n\n # testdata stays excluded: those fixtures' line numbers are pinned to unit\n # tests, and the agent short-circuits them to benign anyway.\n - name: Scan\n if: steps.llm.outputs.skip != 'true'\n run: |\n pipx install opengrep\n opengrep scan --config=auto --dataflow-traces \\\n --exclude testdata --sarif-output=findings.sarif\n\n # Dogfood: `uses: ./` resolves action.yml from this checkout, so PRs run\n # their own action code.\n - name: Triage\n if: steps.llm.outputs.skip != 'true'\n uses: ./\n with:\n provider: openai\n model: deepseek-v4-flash\n base-url: https://api.deepseek.com/v1\n openai-api-key: ${{ secrets.DEEPSEEK_API_KEY }}\n scope: diff\n base-ref: origin/${{ github.base_ref }}\n mode: enforce\n cache-write: branch\n pr-comments: \"true\"\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.event.pull_request.head.sha }}\n\n # The digest, not the report: the step summary caps at 1 MiB, and a large\n # backlog blows past that. The complete report rides in the artifact.\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n # Emitted on every run, gate or no gate: the annotated SARIF relabels\n # benign findings via suppressions[] and deletes nothing, so it is the\n # integration surface for DefectDojo and friends.\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n .sast-triage/cache.json\n retention-days: 7\n\n"}, "86866e08c2fb4d48e557425d003dfeb600c6ae1a": {"t": 1785762229.549279, "n": 3473, "h": "99c85c083bac18da", "c": "# Pull requests: diff scope, enforcing gate, cache committed to the PR's own\n# head branch. One of three triage workflows, split by trigger so each one's\n# scope and gating are readable without cross-referencing the others.\nname: Triage (PR)\n\non:\n pull_request:\n branches: [main]\n\nconcurrency:\n group: triage-pr-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write # commit the cache delta to the PR head branch\n pull-requests: write # inline review comments\n\njobs:\n triage:\n runs-on: ubuntu-latest\n steps:\n # The PR head SHA, not the merge commit: the cache delta is committed on\n # top of it and pushed to the head branch, which has to be a fast-forward.\n # fetch-depth 0 so origin/<base> exists for the diff.\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n ref: ${{ github.event.pull_request.head.sha }}\n\n - name: Check for an API key\n id: llm\n env:\n OPENAI_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n run: |\n # Fork PRs get no secrets. Triage is skipped rather than failed: a\n # contributor cannot fix a missing secret, and a red X they cannot\n # act on teaches everyone to ignore this check.\n if [ -z \"$OPENAI_API_KEY\" ]; then\n echo \"skip=true\" >> \"$GITHUB_OUTPUT\"\n echo \"::notice::no API key (fork PR?) — triage skipped.\"\n else\n echo \"skip=false\" >> \"$GITHUB_OUTPUT\"\n fi\n\n # testdata stays excluded: those fixtures' line numbers are pinned to unit\n # tests, and the agent short-circuits them to benign anyway.\n - name: Scan\n if: steps.llm.outputs.skip != 'true'\n run: |\n pipx install semgrep\n semgrep scan --config=auto --dataflow-traces \\\n --exclude testdata --sarif-output=findings.sarif\n\n # Dogfood: `uses: ./` resolves action.yml from this checkout, so PRs run\n # their own action code.\n - name: Triage\n if: steps.llm.outputs.skip != 'true'\n uses: ./\n with:\n provider: openai\n model: deepseek-v4-flash\n base-url: https://api.deepseek.com/v1\n openai-api-key: ${{ secrets.DEEPSEEK_API_KEY }}\n scope: diff\n base-ref: origin/${{ github.base_ref }}\n mode: enforce\n cache-write: branch\n pr-comments: \"true\"\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.event.pull_request.head.sha }}\n\n # The digest, not the report: the step summary caps at 1 MiB, and a large\n # backlog blows past that. The complete report rides in the artifact.\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n # Emitted on every run, gate or no gate: the annotated SARIF relabels\n # benign findings via suppressions[] and deletes nothing, so it is the\n # integration surface for DefectDojo and friends.\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n .sast-triage/cache.json\n retention-days: 7\n\n"}, "9e7f5f004322940f8951544f93c5def6438acfa6": {"t": 1785762231.5312922, "n": 3470, "h": "b7ea35784612ccc3", "c": "# Pull requests: diff scope, enforcing gate, cache committed to the PR's own\n# head branch. One of three triage workflows, split by trigger so each one's\n# scope and gating are readable without cross-referencing the others.\nname: Triage (PR)\n\non:\n pull_request:\n branches: [main]\n\nconcurrency:\n group: triage-pr-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write # commit the cache delta to the PR head branch\n pull-requests: write # inline review comments\n\njobs:\n triage:\n runs-on: ubuntu-latest\n steps:\n # The PR head SHA, not the merge commit: the cache delta is committed on\n # top of it and pushed to the head branch, which has to be a fast-forward.\n # fetch-depth 0 so origin/<base> exists for the diff.\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n ref: ${{ github.event.pull_request.head.sha }}\n\n - name: Check for an API key\n id: llm\n env:\n OPENAI_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}\n run: |\n # Fork PRs get no secrets. Triage is skipped rather than failed: a\n # contributor cannot fix a missing secret, and a red X they cannot\n # act on teaches everyone to ignore this check.\n if [ -z \"$OPENAI_API_KEY\" ]; then\n echo \"skip=true\" >> \"$GITHUB_OUTPUT\"\n echo \"::notice::no API key (fork PR?) — triage skipped.\"\n else\n echo \"skip=false\" >> \"$GITHUB_OUTPUT\"\n fi\n\n # testdata stays excluded: those fixtures' line numbers are pinned to unit\n # tests, and the agent short-circuits them to benign anyway.\n - name: Scan\n if: steps.llm.outputs.skip != 'true'\n run: |\n pipx install semgrep\n semgrep scan --config=auto --dataflow-traces \\\n --exclude testdata --sarif-output=findings.sarif\n\n # Dogfood: `uses: ./` resolves action.yml from this checkout, so PRs run\n # their own action code.\n - name: Triage\n if: steps.llm.outputs.skip != 'true'\n uses: ./\n with:\n provider: openai\n model: DeepSeek-V4-Pro\n base-url: https://api.deepseek.com/v1\n openai-api-key: ${{ secrets.DEEPSEEK_API_KEY }}\n scope: diff\n base-ref: origin/${{ github.base_ref }}\n mode: enforce\n cache-write: branch\n pr-comments: \"true\"\n link-base: https://github.com/${{ github.repository }}/blob/${{ github.event.pull_request.head.sha }}\n\n # The digest, not the report: the step summary caps at 1 MiB, and a large\n # backlog blows past that. The complete report rides in the artifact.\n - name: Publish digest to run summary\n if: always()\n run: '[ -f triage-digest.md ] && cat triage-digest.md >> \"$GITHUB_STEP_SUMMARY\" || true'\n\n # Emitted on every run, gate or no gate: the annotated SARIF relabels\n # benign findings via suppressions[] and deletes nothing, so it is the\n # integration surface for DefectDojo and friends.\n - name: Upload artifacts\n if: always()\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: triage-${{ github.run_id }}\n path: |\n findings.sarif\n triaged.sarif\n triage-report.md\n triage-digest.md\n .sast-triage/cache.json\n retention-days: 7\n"}}}} |