mirror of
https://github.com/mandiant/capa.git
synced 2026-01-23 01:39:00 -08:00
Compare commits
93 Commits
v9.0.0
...
ci/add-gem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48fc4a54de | ||
|
|
eb0afc806e | ||
|
|
9a09f667bf | ||
|
|
15a1dc3409 | ||
|
|
a18fe34d01 | ||
|
|
edcea18c52 | ||
|
|
92f0306f96 | ||
|
|
f2ed75c339 | ||
|
|
6e18657ca7 | ||
|
|
8ba48d11d0 | ||
|
|
d6f442b5bd | ||
|
|
0da5d7c5b5 | ||
|
|
fa5d9a9302 | ||
|
|
30fb4751f6 | ||
|
|
a8eab7ddf0 | ||
|
|
5ad1dda918 | ||
|
|
eabb2cc809 | ||
|
|
a34c3ecc57 | ||
|
|
d22de5cf7f | ||
|
|
8f78834cae | ||
|
|
08dbb0e02d | ||
|
|
98725c52dc | ||
|
|
eb87153064 | ||
|
|
56aa7176b0 | ||
|
|
8b41671409 | ||
|
|
5dbbc2b468 | ||
|
|
96d1eb64c3 | ||
|
|
9234b33051 | ||
|
|
51f5114ad7 | ||
|
|
4b72f8a872 | ||
|
|
8206a97b0f | ||
|
|
5a33b4b2a8 | ||
|
|
fcfdeec377 | ||
|
|
37a63a751c | ||
|
|
3a9f2136bb | ||
|
|
390e2a6315 | ||
|
|
6a43084915 | ||
|
|
6d7ca57fa9 | ||
|
|
d1090e8391 | ||
|
|
b07efe773b | ||
|
|
9d3d3be21d | ||
|
|
8251a4c16f | ||
|
|
7407cb39ca | ||
|
|
0162e447fd | ||
|
|
829dae388f | ||
|
|
2a4d0ae080 | ||
|
|
d9a754730c | ||
|
|
4acacba9d6 | ||
|
|
d00f172973 | ||
|
|
1572dd87ed | ||
|
|
23a88fae70 | ||
|
|
474e64cd32 | ||
|
|
c664dc662f | ||
|
|
c1c71613a9 | ||
|
|
fa90aae3dc | ||
|
|
7ba02c424e | ||
|
|
f238708ab8 | ||
|
|
9c639005ee | ||
|
|
c37b04fa5f | ||
|
|
dadd536498 | ||
|
|
f3b07dba14 | ||
|
|
66158db197 | ||
|
|
a4285c013e | ||
|
|
6924974b6b | ||
|
|
dc153c4763 | ||
|
|
71a28e4482 | ||
|
|
f6ed36fa0f | ||
|
|
6e68034d57 | ||
|
|
0df50f5d54 | ||
|
|
f1131750cc | ||
|
|
077082a376 | ||
|
|
86318093da | ||
|
|
4ee8a7c6b1 | ||
|
|
151d30bec6 | ||
|
|
3bd339522e | ||
|
|
7ecf292095 | ||
|
|
45ea683d19 | ||
|
|
2b95fa089d | ||
|
|
d3d71f97c8 | ||
|
|
4c9d81072a | ||
|
|
a94c68377a | ||
|
|
14e076864c | ||
|
|
6684f9f890 | ||
|
|
e622989eeb | ||
|
|
9c9dd15bf9 | ||
|
|
06fad4a89e | ||
|
|
e06a0ab75f | ||
|
|
0371ade358 | ||
|
|
80b5a116a5 | ||
|
|
9a270e6bdd | ||
|
|
8773bc77ab | ||
|
|
a278bf593a | ||
|
|
f85cd80d90 |
3
.github/pyinstaller/pyinstaller.spec
vendored
3
.github/pyinstaller/pyinstaller.spec
vendored
@@ -74,6 +74,9 @@ a = Analysis(
|
||||
# only be installed locally.
|
||||
"binaryninja",
|
||||
"ida",
|
||||
# remove once https://github.com/mandiant/capa/issues/2681 has
|
||||
# been addressed by PyInstaller
|
||||
"pkg_resources",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
79
.github/workflows/build.yml
vendored
79
.github/workflows/build.yml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
- '**.md'
|
||||
release:
|
||||
types: [edited, published]
|
||||
workflow_dispatch: # manual trigger for testing
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -22,24 +23,38 @@ jobs:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
- os: ubuntu-22.04
|
||||
# use old linux so that the shared library versioning is more portable
|
||||
artifact_name: capa
|
||||
asset_name: linux
|
||||
python_version: '3.10'
|
||||
- os: ubuntu-20.04
|
||||
- os: ubuntu-22.04-arm
|
||||
artifact_name: capa
|
||||
asset_name: linux-arm64
|
||||
python_version: '3.10'
|
||||
- os: ubuntu-22.04
|
||||
artifact_name: capa
|
||||
asset_name: linux-py312
|
||||
python_version: '3.12'
|
||||
- os: windows-2019
|
||||
- os: windows-2022
|
||||
artifact_name: capa.exe
|
||||
asset_name: windows
|
||||
python_version: '3.10'
|
||||
# Windows 11 ARM64 complains of conflicting package version
|
||||
# Additionally, there is no ARM64 build of Python for Python 3.10 on Windows 11 ARM: https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json
|
||||
#- os: windows-11-arm
|
||||
# artifact_name: capa.exe
|
||||
# asset_name: windows-arm64
|
||||
# python_version: '3.12'
|
||||
- os: macos-13
|
||||
# use older macOS for assumed better portability
|
||||
artifact_name: capa
|
||||
asset_name: macos
|
||||
python_version: '3.10'
|
||||
- os: macos-14
|
||||
artifact_name: capa
|
||||
asset_name: macos-arm64
|
||||
python_version: '3.10'
|
||||
steps:
|
||||
- name: Checkout capa
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
@@ -49,7 +64,7 @@ jobs:
|
||||
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
|
||||
with:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
- if: matrix.os == 'ubuntu-20.04'
|
||||
- if: matrix.os == 'ubuntu-22.04' || matrix.os == 'ubuntu-22.04-arm'
|
||||
run: sudo apt-get install -y libyaml-dev
|
||||
- name: Upgrade pip, setuptools
|
||||
run: python -m pip install --upgrade pip setuptools
|
||||
@@ -59,6 +74,28 @@ jobs:
|
||||
pip install -e .[build]
|
||||
- name: Build standalone executable
|
||||
run: pyinstaller --log-level DEBUG .github/pyinstaller/pyinstaller.spec
|
||||
- name: Does it run without warnings or errors?
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${{ matrix.os }}" == "windows-2022" ]] || [[ "${{ matrix.os }}" == "windows-11-arm" ]]; then
|
||||
EXECUTABLE=".\\dist\\capa"
|
||||
else
|
||||
EXECUTABLE="./dist/capa"
|
||||
fi
|
||||
|
||||
output=$(${EXECUTABLE} --version 2>&1)
|
||||
exit_code=$?
|
||||
|
||||
echo "${output}"
|
||||
echo "${exit_code}"
|
||||
|
||||
if echo "${output}" | grep -iE 'error|warning'; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${exit_code}" -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
- name: Does it run (PE)?
|
||||
run: dist/capa -d "tests/data/Practical Malware Analysis Lab 01-01.dll_"
|
||||
- name: Does it run (Shellcode)?
|
||||
@@ -74,34 +111,6 @@ jobs:
|
||||
name: ${{ matrix.asset_name }}
|
||||
path: dist/${{ matrix.artifact_name }}
|
||||
|
||||
test_run:
|
||||
name: Test run on ${{ matrix.os }} / ${{ matrix.asset_name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [build]
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# OSs not already tested above
|
||||
- os: ubuntu-22.04
|
||||
artifact_name: capa
|
||||
asset_name: linux
|
||||
- os: ubuntu-22.04
|
||||
artifact_name: capa
|
||||
asset_name: linux-py312
|
||||
- os: windows-2022
|
||||
artifact_name: capa.exe
|
||||
asset_name: windows
|
||||
steps:
|
||||
- name: Download ${{ matrix.asset_name }}
|
||||
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
|
||||
with:
|
||||
name: ${{ matrix.asset_name }}
|
||||
- name: Set executable flag
|
||||
if: matrix.os != 'windows-2022'
|
||||
run: chmod +x ${{ matrix.artifact_name }}
|
||||
- name: Run capa
|
||||
run: ./${{ matrix.artifact_name }} -h
|
||||
|
||||
zip_and_upload:
|
||||
# upload zipped binaries to Release page
|
||||
if: github.event_name == 'release'
|
||||
@@ -113,12 +122,18 @@ jobs:
|
||||
include:
|
||||
- asset_name: linux
|
||||
artifact_name: capa
|
||||
- asset_name: linux-arm64
|
||||
artifact_name: capa
|
||||
- asset_name: linux-py312
|
||||
artifact_name: capa
|
||||
- asset_name: windows
|
||||
artifact_name: capa.exe
|
||||
#- asset_name: windows-arm64
|
||||
# artifact_name: capa.exe
|
||||
- asset_name: macos
|
||||
artifact_name: capa
|
||||
- asset_name: macos-arm64
|
||||
artifact_name: capa
|
||||
steps:
|
||||
- name: Download ${{ matrix.asset_name }}
|
||||
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
|
||||
|
||||
304
.github/workflows/gemini-cli.yml
vendored
Normal file
304
.github/workflows/gemini-cli.yml
vendored
Normal file
@@ -0,0 +1,304 @@
|
||||
name: '💬 Gemini CLI'
|
||||
|
||||
on:
|
||||
pull_request_review_comment:
|
||||
types:
|
||||
- 'created'
|
||||
pull_request_review:
|
||||
types:
|
||||
- 'submitted'
|
||||
issue_comment:
|
||||
types:
|
||||
- 'created'
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.event.issue.number }}'
|
||||
cancel-in-progress: |-
|
||||
${{ github.event.sender.type == 'User' && ( github.event.issue.author_association == 'OWNER' || github.event.issue.author_association == 'MEMBER' || github.event.issue.author_association == 'COLLABORATOR') }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: 'bash'
|
||||
|
||||
permissions:
|
||||
contents: 'write'
|
||||
id-token: 'write'
|
||||
pull-requests: 'write'
|
||||
issues: 'write'
|
||||
|
||||
jobs:
|
||||
gemini-cli:
|
||||
# This condition is complex to ensure we only run when explicitly invoked.
|
||||
if: |-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
github.event_name == 'issues' && github.event.action == 'opened' &&
|
||||
contains(github.event.issue.body, '@gemini-cli') &&
|
||||
!contains(github.event.issue.body, '@gemini-cli /review') &&
|
||||
!contains(github.event.issue.body, '@gemini-cli /triage') &&
|
||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.issue.author_association)
|
||||
) ||
|
||||
(
|
||||
(
|
||||
github.event_name == 'issue_comment' ||
|
||||
github.event_name == 'pull_request_review_comment'
|
||||
) &&
|
||||
contains(github.event.comment.body, '@gemini-cli') &&
|
||||
!contains(github.event.comment.body, '@gemini-cli /review') &&
|
||||
!contains(github.event.comment.body, '@gemini-cli /triage') &&
|
||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)
|
||||
) ||
|
||||
(
|
||||
github.event_name == 'pull_request_review' &&
|
||||
contains(github.event.review.body, '@gemini-cli') &&
|
||||
!contains(github.event.review.body, '@gemini-cli /review') &&
|
||||
!contains(github.event.review.body, '@gemini-cli /triage') &&
|
||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.review.author_association)
|
||||
)
|
||||
timeout-minutes: 10
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- name: 'Generate GitHub App Token'
|
||||
id: 'generate_token'
|
||||
if: |-
|
||||
${{ vars.APP_ID }}
|
||||
uses: 'actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e' # ratchet:actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: '${{ vars.APP_ID }}'
|
||||
private-key: '${{ secrets.APP_PRIVATE_KEY }}'
|
||||
|
||||
- name: 'Get context from event'
|
||||
id: 'get_context'
|
||||
env:
|
||||
EVENT_NAME: '${{ github.event_name }}'
|
||||
EVENT_PAYLOAD: '${{ toJSON(github.event) }}'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
|
||||
USER_REQUEST=""
|
||||
ISSUE_NUMBER=""
|
||||
IS_PR="false"
|
||||
|
||||
if [[ "${EVENT_NAME}" == "issues" ]]; then
|
||||
USER_REQUEST=$(echo "${EVENT_PAYLOAD}" | jq -r .issue.body)
|
||||
ISSUE_NUMBER=$(echo "${EVENT_PAYLOAD}" | jq -r .issue.number)
|
||||
elif [[ "${EVENT_NAME}" == "issue_comment" ]]; then
|
||||
USER_REQUEST=$(echo "${EVENT_PAYLOAD}" | jq -r .comment.body)
|
||||
ISSUE_NUMBER=$(echo "${EVENT_PAYLOAD}" | jq -r .issue.number)
|
||||
if [[ $(echo "${EVENT_PAYLOAD}" | jq -r .issue.pull_request) != "null" ]]; then
|
||||
IS_PR="true"
|
||||
fi
|
||||
elif [[ "${EVENT_NAME}" == "pull_request_review" ]]; then
|
||||
USER_REQUEST=$(echo "${EVENT_PAYLOAD}" | jq -r .review.body)
|
||||
ISSUE_NUMBER=$(echo "${EVENT_PAYLOAD}" | jq -r .pull_request.number)
|
||||
IS_PR="true"
|
||||
elif [[ "${EVENT_NAME}" == "pull_request_review_comment" ]]; then
|
||||
USER_REQUEST=$(echo "${EVENT_PAYLOAD}" | jq -r .comment.body)
|
||||
ISSUE_NUMBER=$(echo "${EVENT_PAYLOAD}" | jq -r .pull_request.number)
|
||||
IS_PR="true"
|
||||
fi
|
||||
|
||||
# Clean up user request
|
||||
USER_REQUEST=$(echo "${USER_REQUEST}" | sed 's/.*@gemini-cli//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
|
||||
{
|
||||
echo "user_request=${USER_REQUEST}"
|
||||
echo "issue_number=${ISSUE_NUMBER}"
|
||||
echo "is_pr=${IS_PR}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: 'Set up git user for commits'
|
||||
run: |-
|
||||
git config --global user.name 'gemini-cli[bot]'
|
||||
git config --global user.email 'gemini-cli[bot]@users.noreply.github.com'
|
||||
|
||||
- name: 'Checkout PR branch'
|
||||
if: |-
|
||||
${{ steps.get_context.outputs.is_pr == 'true' }}
|
||||
uses: 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # ratchet:actions/checkout@v4
|
||||
with:
|
||||
token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
repository: '${{ github.repository }}'
|
||||
ref: 'refs/pull/${{ steps.get_context.outputs.issue_number }}/head'
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Checkout main branch'
|
||||
if: |-
|
||||
${{ steps.get_context.outputs.is_pr == 'false' }}
|
||||
uses: 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # ratchet:actions/checkout@v4
|
||||
with:
|
||||
token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
repository: '${{ github.repository }}'
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Acknowledge request'
|
||||
env:
|
||||
GITHUB_ACTOR: '${{ github.actor }}'
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
ISSUE_NUMBER: '${{ steps.get_context.outputs.issue_number }}'
|
||||
REPOSITORY: '${{ github.repository }}'
|
||||
REQUEST_TYPE: '${{ steps.get_context.outputs.request_type }}'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
MESSAGE="@${GITHUB_ACTOR} I've received your request and I'm working on it now! 🤖"
|
||||
if [[ -n "${MESSAGE}" ]]; then
|
||||
gh issue comment "${ISSUE_NUMBER}" \
|
||||
--body "${MESSAGE}" \
|
||||
--repo "${REPOSITORY}"
|
||||
fi
|
||||
|
||||
- name: 'Get description'
|
||||
id: 'get_description'
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
IS_PR: '${{ steps.get_context.outputs.is_pr }}'
|
||||
ISSUE_NUMBER: '${{ steps.get_context.outputs.issue_number }}'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
if [[ "${IS_PR}" == "true" ]]; then
|
||||
DESCRIPTION=$(gh pr view "${ISSUE_NUMBER}" --json body --template '{{.body}}')
|
||||
else
|
||||
DESCRIPTION=$(gh issue view "${ISSUE_NUMBER}" --json body --template '{{.body}}')
|
||||
fi
|
||||
{
|
||||
echo "description<<EOF"
|
||||
echo "${DESCRIPTION}"
|
||||
echo "EOF"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: 'Get comments'
|
||||
id: 'get_comments'
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
IS_PR: '${{ steps.get_context.outputs.is_pr }}'
|
||||
ISSUE_NUMBER: '${{ steps.get_context.outputs.issue_number }}'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
if [[ "${IS_PR}" == "true" ]]; then
|
||||
COMMENTS=$(gh pr view "${ISSUE_NUMBER}" --json comments --template '{{range .comments}}{{.author.login}}: {{.body}}{{"\n"}}{{end}}')
|
||||
else
|
||||
COMMENTS=$(gh issue view "${ISSUE_NUMBER}" --json comments --template '{{range .comments}}{{.author.login}}: {{.body}}{{"\n"}}{{end}}')
|
||||
fi
|
||||
{
|
||||
echo "comments<<EOF"
|
||||
echo "${COMMENTS}"
|
||||
echo "EOF"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: 'Run Gemini'
|
||||
id: 'run_gemini'
|
||||
uses: 'google-github-actions/run-gemini-cli@v0'
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
REPOSITORY: '${{ github.repository }}'
|
||||
USER_REQUEST: '${{ steps.get_context.outputs.user_request }}'
|
||||
ISSUE_NUMBER: '${{ steps.get_context.outputs.issue_number }}'
|
||||
IS_PR: '${{ steps.get_context.outputs.is_pr }}'
|
||||
with:
|
||||
gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
|
||||
gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}'
|
||||
gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}'
|
||||
gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}'
|
||||
gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
|
||||
use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}'
|
||||
use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}'
|
||||
settings: |-
|
||||
{
|
||||
"maxSessionTurns": 50,
|
||||
"telemetry": {
|
||||
"enabled": false,
|
||||
"target": "gcp"
|
||||
}
|
||||
}
|
||||
prompt: |-
|
||||
## Role
|
||||
|
||||
You are a helpful AI assistant invoked via a CLI interface in a GitHub workflow. You have access to tools to interact with the repository and respond to the user.
|
||||
|
||||
## Context
|
||||
|
||||
- **Repository**: `${{ github.repository }}`
|
||||
- **Triggering Event**: `${{ github.event_name }}`
|
||||
- **Issue/PR Number**: `${{ steps.get_context.outputs.issue_number }}`
|
||||
- **Is this a PR?**: `${{ steps.get_context.outputs.is_pr }}`
|
||||
- **Issue/PR Description**:
|
||||
`${{ steps.get_description.outputs.description }}`
|
||||
- **Comments**:
|
||||
`${{ steps.get_comments.outputs.comments }}`
|
||||
|
||||
## User Request
|
||||
|
||||
The user has sent the following request:
|
||||
`${{ steps.get_context.outputs.user_request }}`
|
||||
|
||||
## How to Respond to Issues, PR Comments, and Questions
|
||||
|
||||
This workflow supports three main scenarios:
|
||||
|
||||
1. **Creating a Fix for an Issue**
|
||||
- Carefully read the user request and the related issue or PR description.
|
||||
- Use available tools to gather all relevant context (e.g., `gh issue view`, `gh pr view`, `gh pr diff`, `cat`, `head`, `tail`).
|
||||
- Identify the root cause of the problem before proceeding.
|
||||
- **Show and maintain a plan as a checklist**:
|
||||
- At the very beginning, outline the steps needed to resolve the issue or address the request and post them as a checklist comment on the issue or PR (use GitHub markdown checkboxes: `- [ ] Task`).
|
||||
- Example:
|
||||
```
|
||||
### Plan
|
||||
- [ ] Investigate the root cause
|
||||
- [ ] Implement the fix in `file.py`
|
||||
- [ ] Add/modify tests
|
||||
- [ ] Update documentation
|
||||
- [ ] Verify the fix and close the issue
|
||||
```
|
||||
- Use: `gh pr comment "${ISSUE_NUMBER}" --body "<plan>"` or `gh issue comment "${ISSUE_NUMBER}" --body "<plan>"` to post the initial plan.
|
||||
- As you make progress, keep the checklist visible and up to date by editing the same comment (check off completed tasks with `- [x]`).
|
||||
- To update the checklist:
|
||||
1. Find the comment ID for the checklist (use `gh pr comment list "${ISSUE_NUMBER}"` or `gh issue comment list "${ISSUE_NUMBER}"`).
|
||||
2. Edit the comment with the updated checklist:
|
||||
- For PRs: `gh pr comment --edit <comment-id> --body "<updated plan>"`
|
||||
- For Issues: `gh issue comment --edit <comment-id> --body "<updated plan>"`
|
||||
3. The checklist should only be maintained as a comment on the issue or PR. Do not track or update the checklist in code files.
|
||||
- If the fix requires code changes, determine which files and lines are affected. If clarification is needed, note any questions for the user.
|
||||
- Make the necessary code or documentation changes using the available tools (e.g., `write_file`). Ensure all changes follow project conventions and best practices. Reference all shell variables as `"${VAR}"` (with quotes and braces) to prevent errors.
|
||||
- Run any relevant tests or checks to verify the fix works as intended. If possible, provide evidence (test output, screenshots, etc.) that the issue is resolved.
|
||||
- **Branching and Committing**:
|
||||
- **NEVER commit directly to the `main` branch.**
|
||||
- If you are working on a **pull request** (`IS_PR` is `true`), the correct branch is already checked out. Simply commit and push to it.
|
||||
- `git add .`
|
||||
- `git commit -m "feat: <describe the change>"`
|
||||
- `git push`
|
||||
- If you are working on an **issue** (`IS_PR` is `false`), create a new branch for your changes. A good branch name would be `issue/${ISSUE_NUMBER}/<short-description>`.
|
||||
- `git checkout -b issue/${ISSUE_NUMBER}/my-fix`
|
||||
- `git add .`
|
||||
- `git commit -m "feat: <describe the fix>"`
|
||||
- `git push origin issue/${ISSUE_NUMBER}/my-fix`
|
||||
- After pushing, you can create a pull request: `gh pr create --title "Fixes #${ISSUE_NUMBER}: <short title>" --body "This PR addresses issue #${ISSUE_NUMBER}."`
|
||||
- Summarize what was changed and why in a markdown file: `write_file("response.md", "<your response here>")`
|
||||
- Post the response as a comment:
|
||||
- For PRs: `gh pr comment "${ISSUE_NUMBER}" --body-file response.md`
|
||||
- For Issues: `gh issue comment "${ISSUE_NUMBER}" --body-file response.md`
|
||||
|
||||
2. **Addressing Comments on a Pull Request**
|
||||
- Read the specific comment and the context of the PR.
|
||||
- Use tools like `gh pr view`, `gh pr diff`, and `cat` to understand the code and discussion.
|
||||
- If the comment requests a change or clarification, follow the same process as for fixing an issue: create a checklist plan, implement, test, and commit any required changes, updating the checklist as you go.
|
||||
- **Committing Changes**: The correct PR branch is already checked out. Simply add, commit, and push your changes.
|
||||
- `git add .`
|
||||
- `git commit -m "fix: address review comments"`
|
||||
- `git push`
|
||||
- If the comment is a question, answer it directly and clearly, referencing code or documentation as needed.
|
||||
- Document your response in `response.md` and post it as a PR comment: `gh pr comment "${ISSUE_NUMBER}" --body-file response.md`
|
||||
|
||||
3. **Answering Any Question on an Issue**
|
||||
- Read the question and the full issue context using `gh issue view` and related tools.
|
||||
- Research or analyze the codebase as needed to provide an accurate answer.
|
||||
- If the question requires code or documentation changes, follow the fix process above, including creating and updating a checklist plan and **creating a new branch for your changes as described in section 1.**
|
||||
- Write a clear, concise answer in `response.md` and post it as an issue comment: `gh issue comment "${ISSUE_NUMBER}" --body-file response.md`
|
||||
|
||||
## Guidelines
|
||||
|
||||
- **Be concise and actionable.** Focus on solving the user's problem efficiently.
|
||||
- **Always commit and push your changes if you modify code or documentation.**
|
||||
- **If you are unsure about the fix or answer, explain your reasoning and ask clarifying questions.**
|
||||
- **Follow project conventions and best practices.**
|
||||
130
.github/workflows/gemini-issue-automated-triage.yml
vendored
Normal file
130
.github/workflows/gemini-issue-automated-triage.yml
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
name: '🏷️ Gemini Automated Issue Triage'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- 'opened'
|
||||
- 'reopened'
|
||||
issue_comment:
|
||||
types:
|
||||
- 'created'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: 'issue number to triage'
|
||||
required: true
|
||||
type: 'number'
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.event.issue.number }}'
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: 'bash'
|
||||
|
||||
permissions:
|
||||
contents: 'read'
|
||||
id-token: 'write'
|
||||
issues: 'write'
|
||||
statuses: 'write'
|
||||
|
||||
jobs:
|
||||
triage-issue:
|
||||
if: |-
|
||||
github.event_name == 'issues' ||
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
github.event_name == 'issue_comment' &&
|
||||
contains(github.event.comment.body, '@gemini-cli /triage') &&
|
||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)
|
||||
)
|
||||
timeout-minutes: 5
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- name: 'Checkout repository'
|
||||
uses: 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # ratchet:actions/checkout@v4
|
||||
|
||||
- name: 'Generate GitHub App Token'
|
||||
id: 'generate_token'
|
||||
if: |-
|
||||
${{ vars.APP_ID }}
|
||||
uses: 'actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e' # ratchet:actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: '${{ vars.APP_ID }}'
|
||||
private-key: '${{ secrets.APP_PRIVATE_KEY }}'
|
||||
|
||||
- name: 'Run Gemini Issue Triage'
|
||||
uses: 'google-github-actions/run-gemini-cli@v0'
|
||||
id: 'gemini_issue_triage'
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
ISSUE_TITLE: '${{ github.event.issue.title }}'
|
||||
ISSUE_BODY: '${{ github.event.issue.body }}'
|
||||
ISSUE_NUMBER: '${{ github.event.issue.number }}'
|
||||
REPOSITORY: '${{ github.repository }}'
|
||||
with:
|
||||
gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}'
|
||||
gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}'
|
||||
gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}'
|
||||
gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}'
|
||||
gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
|
||||
gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
|
||||
use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}'
|
||||
use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}'
|
||||
settings: |-
|
||||
{
|
||||
"maxSessionTurns": 25,
|
||||
"coreTools": [
|
||||
"run_shell_command(echo)",
|
||||
"run_shell_command(gh label list)",
|
||||
"run_shell_command(gh issue edit)"
|
||||
],
|
||||
"telemetry": {
|
||||
"enabled": false,
|
||||
"target": "gcp"
|
||||
}
|
||||
}
|
||||
prompt: |-
|
||||
## Role
|
||||
|
||||
You are an issue triage assistant. Analyze the current GitHub issue
|
||||
and apply the most appropriate existing labels. Use the available
|
||||
tools to gather information; do not ask for information to be
|
||||
provided.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Run: `gh label list` to get all available labels.
|
||||
2. Review the issue title and body provided in the environment
|
||||
variables: "${ISSUE_TITLE}" and "${ISSUE_BODY}".
|
||||
3. Select the most relevant labels from the existing labels. If
|
||||
available, set labels that follow the `kind/*`, `area/*`, and
|
||||
`priority/*` patterns.
|
||||
4. Apply the selected labels to this issue using:
|
||||
`gh issue edit "${ISSUE_NUMBER}" --add-label "label1,label2"`
|
||||
5. If the "status/needs-triage" label is present, remove it using:
|
||||
`gh issue edit "${ISSUE_NUMBER}" --remove-label "status/needs-triage"`
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Only use labels that already exist in the repository
|
||||
- Do not add comments or modify the issue content
|
||||
- Triage only the current issue
|
||||
- Assign all applicable labels based on the issue content
|
||||
- Reference all shell variables as "${VAR}" (with quotes and braces)
|
||||
|
||||
- name: 'Post Issue Triage Failure Comment'
|
||||
if: |-
|
||||
${{ failure() && steps.gemini_issue_triage.outcome == 'failure' }}
|
||||
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
|
||||
with:
|
||||
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
script: |-
|
||||
github.rest.issues.createComment({
|
||||
owner: '${{ github.repository }}'.split('/')[0],
|
||||
repo: '${{ github.repository }}'.split('/')[1],
|
||||
issue_number: '${{ github.event.issue.number }}',
|
||||
body: 'There is a problem with the Gemini CLI issue triaging. Please check the [action logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.'
|
||||
})
|
||||
123
.github/workflows/gemini-issue-scheduled-triage.yml
vendored
Normal file
123
.github/workflows/gemini-issue-scheduled-triage.yml
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
name: '📋 Gemini Scheduled Issue Triage'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 * * * *' # Runs every hour
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}'
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: 'bash'
|
||||
|
||||
permissions:
|
||||
contents: 'read'
|
||||
id-token: 'write'
|
||||
issues: 'write'
|
||||
statuses: 'write'
|
||||
|
||||
jobs:
|
||||
triage-issues:
|
||||
timeout-minutes: 5
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- name: 'Checkout repository'
|
||||
uses: 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # ratchet:actions/checkout@v4
|
||||
|
||||
- name: 'Generate GitHub App Token'
|
||||
id: 'generate_token'
|
||||
if: |-
|
||||
${{ vars.APP_ID }}
|
||||
uses: 'actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e' # ratchet:actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: '${{ vars.APP_ID }}'
|
||||
private-key: '${{ secrets.APP_PRIVATE_KEY }}'
|
||||
|
||||
- name: 'Find untriaged issues'
|
||||
id: 'find_issues'
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
GITHUB_REPOSITORY: '${{ github.repository }}'
|
||||
GITHUB_OUTPUT: '${{ github.output }}'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
|
||||
echo '🔍 Finding issues without labels...'
|
||||
NO_LABEL_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \
|
||||
--search 'is:open is:issue no:label' --json number,title,body)"
|
||||
|
||||
echo '🏷️ Finding issues that need triage...'
|
||||
NEED_TRIAGE_ISSUES="$(gh issue list --repo "${GITHUB_REPOSITORY}" \
|
||||
--search 'is:open is:issue label:"status/needs-triage"' --json number,title,body)"
|
||||
|
||||
echo '🔄 Merging and deduplicating issues...'
|
||||
ISSUES="$(echo "${NO_LABEL_ISSUES}" "${NEED_TRIAGE_ISSUES}" | jq -c -s 'add | unique_by(.number)')"
|
||||
|
||||
echo '📝 Setting output for GitHub Actions...'
|
||||
echo "issues_to_triage=${ISSUES}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
ISSUE_COUNT="$(echo "${ISSUES}" | jq 'length')"
|
||||
echo "✅ Found ${ISSUE_COUNT} issues to triage! 🎯"
|
||||
|
||||
- name: 'Run Gemini Issue Triage'
|
||||
if: |-
|
||||
${{ steps.find_issues.outputs.issues_to_triage != '[]' }}
|
||||
uses: 'google-github-actions/run-gemini-cli@v0'
|
||||
id: 'gemini_issue_triage'
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
ISSUES_TO_TRIAGE: '${{ steps.find_issues.outputs.issues_to_triage }}'
|
||||
REPOSITORY: '${{ github.repository }}'
|
||||
with:
|
||||
gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}'
|
||||
gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}'
|
||||
gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}'
|
||||
gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}'
|
||||
gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
|
||||
gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
|
||||
use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}'
|
||||
use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}'
|
||||
settings: |-
|
||||
{
|
||||
"maxSessionTurns": 25,
|
||||
"coreTools": [
|
||||
"run_shell_command(echo)",
|
||||
"run_shell_command(gh label list)",
|
||||
"run_shell_command(gh issue edit)",
|
||||
"run_shell_command(gh issue list)"
|
||||
],
|
||||
"telemetry": {
|
||||
"enabled": false,
|
||||
"target": "gcp"
|
||||
}
|
||||
}
|
||||
prompt: |-
|
||||
## Role
|
||||
|
||||
You are an issue triage assistant. Analyze issues and apply
|
||||
appropriate labels. Use the available tools to gather information;
|
||||
do not ask for information to be provided.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Run: `gh label list`
|
||||
2. Check environment variable: "${ISSUES_TO_TRIAGE}" (JSON array
|
||||
of issues)
|
||||
3. For each issue, apply labels:
|
||||
`gh issue edit "${ISSUE_NUMBER}" --add-label "label1,label2"`.
|
||||
If available, set labels that follow the `kind/*`, `area/*`,
|
||||
and `priority/*` patterns.
|
||||
4. For each issue, if the `status/needs-triage` label is present,
|
||||
remove it using:
|
||||
`gh issue edit "${ISSUE_NUMBER}" --remove-label "status/needs-triage"`
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Only use existing repository labels
|
||||
- Do not add comments
|
||||
- Triage each issue independently
|
||||
- Reference all shell variables as "${VAR}" (with quotes and braces)
|
||||
456
.github/workflows/gemini-pr-review.yml
vendored
Normal file
456
.github/workflows/gemini-pr-review.yml
vendored
Normal file
@@ -0,0 +1,456 @@
|
||||
name: '🧐 Gemini Pull Request Review'
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- 'opened'
|
||||
- 'reopened'
|
||||
issue_comment:
|
||||
types:
|
||||
- 'created'
|
||||
pull_request_review_comment:
|
||||
types:
|
||||
- 'created'
|
||||
pull_request_review:
|
||||
types:
|
||||
- 'submitted'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'PR number to review'
|
||||
required: true
|
||||
type: 'number'
|
||||
|
||||
concurrency:
|
||||
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: 'bash'
|
||||
|
||||
permissions:
|
||||
contents: 'read'
|
||||
id-token: 'write'
|
||||
issues: 'write'
|
||||
pull-requests: 'write'
|
||||
statuses: 'write'
|
||||
|
||||
jobs:
|
||||
review-pr:
|
||||
if: |-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
github.event_name == 'pull_request' &&
|
||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association)
|
||||
) ||
|
||||
(
|
||||
(
|
||||
(
|
||||
github.event_name == 'issue_comment' &&
|
||||
github.event.issue.pull_request
|
||||
) ||
|
||||
github.event_name == 'pull_request_review_comment'
|
||||
) &&
|
||||
contains(github.event.comment.body, '@gemini-cli /review') &&
|
||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)
|
||||
) ||
|
||||
(
|
||||
github.event_name == 'pull_request_review' &&
|
||||
contains(github.event.review.body, '@gemini-cli /review') &&
|
||||
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.review.author_association)
|
||||
)
|
||||
timeout-minutes: 5
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- name: 'Checkout PR code'
|
||||
uses: 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' # ratchet:actions/checkout@v4
|
||||
|
||||
- name: 'Generate GitHub App Token'
|
||||
id: 'generate_token'
|
||||
if: |-
|
||||
${{ vars.APP_ID }}
|
||||
uses: 'actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e' # ratchet:actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: '${{ vars.APP_ID }}'
|
||||
private-key: '${{ secrets.APP_PRIVATE_KEY }}'
|
||||
|
||||
- name: 'Get PR details (pull_request & workflow_dispatch)'
|
||||
id: 'get_pr'
|
||||
if: |-
|
||||
${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }}
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
EVENT_NAME: '${{ github.event_name }}'
|
||||
WORKFLOW_PR_NUMBER: '${{ github.event.inputs.pr_number }}'
|
||||
PULL_REQUEST_NUMBER: '${{ github.event.pull_request.number }}'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${EVENT_NAME}" = "workflow_dispatch" ]]; then
|
||||
PR_NUMBER="${WORKFLOW_PR_NUMBER}"
|
||||
else
|
||||
PR_NUMBER="${PULL_REQUEST_NUMBER}"
|
||||
fi
|
||||
|
||||
echo "pr_number=${PR_NUMBER}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
# Get PR details
|
||||
PR_DATA="$(gh pr view "${PR_NUMBER}" --json title,body,additions,deletions,changedFiles,baseRefName,headRefName)"
|
||||
echo "pr_data=${PR_DATA}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
# Get file changes
|
||||
CHANGED_FILES="$(gh pr diff "${PR_NUMBER}" --name-only)"
|
||||
{
|
||||
echo "changed_files<<EOF"
|
||||
echo "${CHANGED_FILES}"
|
||||
echo "EOF"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
|
||||
- name: 'Get PR details (issue_comment)'
|
||||
id: 'get_pr_comment'
|
||||
if: |-
|
||||
${{ github.event_name == 'issue_comment' }}
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
COMMENT_BODY: '${{ github.event.comment.body }}'
|
||||
PR_NUMBER: '${{ github.event.issue.number }}'
|
||||
run: |-
|
||||
set -euo pipefail
|
||||
|
||||
echo "pr_number=${PR_NUMBER}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
# Extract additional instructions from comment
|
||||
ADDITIONAL_INSTRUCTIONS="$(
|
||||
echo "${COMMENT_BODY}" | sed 's/.*@gemini-cli \/review//' | xargs
|
||||
)"
|
||||
echo "additional_instructions=${ADDITIONAL_INSTRUCTIONS}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
# Get PR details
|
||||
PR_DATA="$(gh pr view "${PR_NUMBER}" --json title,body,additions,deletions,changedFiles,baseRefName,headRefName)"
|
||||
echo "pr_data=${PR_DATA}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
# Get file changes
|
||||
CHANGED_FILES="$(gh pr diff "${PR_NUMBER}" --name-only)"
|
||||
{
|
||||
echo "changed_files<<EOF"
|
||||
echo "${CHANGED_FILES}"
|
||||
echo "EOF"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: 'Run Gemini PR Review'
|
||||
uses: 'google-github-actions/run-gemini-cli@v0'
|
||||
id: 'gemini_pr_review'
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
PR_NUMBER: '${{ steps.get_pr.outputs.pr_number || steps.get_pr_comment.outputs.pr_number }}'
|
||||
PR_DATA: '${{ steps.get_pr.outputs.pr_data || steps.get_pr_comment.outputs.pr_data }}'
|
||||
CHANGED_FILES: '${{ steps.get_pr.outputs.changed_files || steps.get_pr_comment.outputs.changed_files }}'
|
||||
ADDITIONAL_INSTRUCTIONS: '${{ steps.get_pr.outputs.additional_instructions || steps.get_pr_comment.outputs.additional_instructions }}'
|
||||
REPOSITORY: '${{ github.repository }}'
|
||||
with:
|
||||
gemini_cli_version: '${{ vars.GEMINI_CLI_VERSION }}'
|
||||
gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}'
|
||||
gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}'
|
||||
gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}'
|
||||
gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
|
||||
gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
|
||||
use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}'
|
||||
use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}'
|
||||
settings: |-
|
||||
{
|
||||
"maxSessionTurns": 20,
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"command": "docker",
|
||||
"args": [
|
||||
"run",
|
||||
"-i",
|
||||
"--rm",
|
||||
"-e",
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
||||
"ghcr.io/github/github-mcp-server"
|
||||
],
|
||||
"includeTools": [
|
||||
"create_pending_pull_request_review",
|
||||
"add_comment_to_pending_review",
|
||||
"submit_pending_pull_request_review"
|
||||
],
|
||||
"env": {
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"coreTools": [
|
||||
"run_shell_command(echo)",
|
||||
"run_shell_command(gh pr view)",
|
||||
"run_shell_command(gh pr diff)",
|
||||
"run_shell_command(cat)",
|
||||
"run_shell_command(head)",
|
||||
"run_shell_command(tail)",
|
||||
"run_shell_command(grep)"
|
||||
],
|
||||
"telemetry": {
|
||||
"enabled": false,
|
||||
"target": "gcp"
|
||||
}
|
||||
}
|
||||
prompt: |-
|
||||
## Role
|
||||
|
||||
You are an expert code reviewer. You have access to tools to gather
|
||||
PR information and perform the review on GitHub. Use the available tools to
|
||||
gather information; do not ask for information to be provided.
|
||||
|
||||
## Requirements
|
||||
1. All feedback must be left on GitHub.
|
||||
2. Any output that is not left in GitHub will not be seen.
|
||||
|
||||
## Steps
|
||||
|
||||
Start by running these commands to gather the required data:
|
||||
1. Run: echo $"{REPOSITORY}" to get the github repository in <OWNER>/<REPO> format
|
||||
2. Run: echo "${PR_DATA}" to get PR details (JSON format)
|
||||
3. Run: echo "${CHANGED_FILES}" to get the list of changed files
|
||||
4. Run: echo "${PR_NUMBER}" to get the PR number
|
||||
5. Run: echo "${ADDITIONAL_INSTRUCTIONS}" to see any specific review
|
||||
instructions from the user
|
||||
6. Run: gh pr diff "${PR_NUMBER}" to see the full diff and reference
|
||||
Context section to understand it
|
||||
7. For any specific files, use: cat filename, head -50 filename, or
|
||||
tail -50 filename
|
||||
8. If ADDITIONAL_INSTRUCTIONS contains text, prioritize those
|
||||
specific areas or focus points in your review. Common instruction
|
||||
examples: "focus on security", "check performance", "review error
|
||||
handling", "check for breaking changes"
|
||||
|
||||
## Guideline
|
||||
### Core Guideline(Always applicable)
|
||||
|
||||
1. Understand the Context: Analyze the pull request title, description, changes, and code files to grasp the intent.
|
||||
2. Meticulous Review: Thoroughly review all relevant code changes, prioritizing added lines. Consider the specified
|
||||
focus areas and any provided style guide.
|
||||
3. Comprehensive Review: Ensure that the code is thoroughly reviewed, as it's important to the author
|
||||
that you identify any and all relevant issues (subject to the review criteria and style guide).
|
||||
Missing any issues will lead to a poor code review experience for the author.
|
||||
4. Constructive Feedback:
|
||||
* Provide clear explanations for each concern.
|
||||
* Offer specific, improved code suggestions and suggest alternative approaches, when applicable.
|
||||
Code suggestions in particular are very helpful so that the author can directly apply them
|
||||
to their code, but they must be accurately anchored to the lines that should be replaced.
|
||||
5. Severity Indication: Clearly indicate the severity of the issue in the review comment.
|
||||
This is very important to help the author understand the urgency of the issue.
|
||||
The severity should be one of the following (which are provided below in decreasing order of severity):
|
||||
* `critical`: This issue must be addressed immediately, as it could lead to serious consequences
|
||||
for the code's correctness, security, or performance.
|
||||
* `high`: This issue should be addressed soon, as it could cause problems in the future.
|
||||
* `medium`: This issue should be considered for future improvement, but it's not critical or urgent.
|
||||
* `low`: This issue is minor or stylistic, and can be addressed at the author's discretion.
|
||||
6. Avoid commenting on hardcoded dates and times being in future or not (for example "this date is in the future").
|
||||
* Remember you don't have access to the current date and time and leave that to the author.
|
||||
7. Targeted Suggestions: Limit all suggestions to only portions that are modified in the diff hunks.
|
||||
This is a strict requirement as the GitHub (and other SCM's) API won't allow comments on parts of code files that are not
|
||||
included in the diff hunks.
|
||||
8. Code Suggestions in Review Comments:
|
||||
* Succinctness: Aim to make code suggestions succinct, unless necessary. Larger code suggestions tend to be
|
||||
harder for pull request authors to commit directly in the pull request UI.
|
||||
* Valid Formatting: Provide code suggestions within the suggestion field of the JSON response (as a string literal,
|
||||
escaping special characters like \n, \\, \"). Do not include markdown code blocks in the suggestion field.
|
||||
Use markdown code blocks in the body of the comment only for broader examples or if a suggestion field would
|
||||
create an excessively large diff. Prefer the suggestion field for specific, targeted code changes.
|
||||
* Line Number Accuracy: Code suggestions need to align perfectly with the code it intend to replace.
|
||||
Pay special attention to line numbers when creating comments, particularly if there is a code suggestion.
|
||||
Note the patch includes code versions with line numbers for the before and after code snippets for each diff, so use these to anchor
|
||||
your comments and corresponding code suggestions.
|
||||
* Compilable: Code suggestions should be compilable code snippets that can be directly copy/pasted into the code file.
|
||||
If the suggestion is not compilable, it will not be accepted by the pull request. Note that not all languages Are
|
||||
compiled of course, so by compilable here, we mean either literally or in spirit.
|
||||
* Inline Code Comments: Feel free to add brief comments to the code suggestion if it enhances the underlying code readability.
|
||||
Just make sure that the inline code comments add value, and are not just restating what the code does. Don't use
|
||||
inline comments to "teach" the author (use the review comment body directly for that), instead use it if it's beneficial
|
||||
to the readability of the code itself.
|
||||
10. Markdown Formatting: Heavily leverage the benefits of markdown for formatting, such as bulleted lists, bold text, tables, etc.
|
||||
11. Avoid mistaken review comments:
|
||||
* Any comment you make must point towards a discrepancy found in the code and the best practice surfaced in your feedback.
|
||||
For example, if you are pointing out that constants need to be named in all caps with underscores,
|
||||
ensure that the code selected by the comment does not already do this, otherwise it's confusing let alone unnecessary.
|
||||
12. Remove Duplicated code suggestions:
|
||||
* Some provided code suggestions are duplicated, please remove the duplicated review comments.
|
||||
13. Don't Approve The Pull Request
|
||||
14. Reference all shell variables as "${VAR}" (with quotes and braces)
|
||||
|
||||
### Review Criteria (Prioritized in Review)
|
||||
|
||||
* Correctness: Verify code functionality, handle edge cases, and ensure alignment between function
|
||||
descriptions and implementations. Consider common correctness issues (logic errors, error handling,
|
||||
race conditions, data validation, API usage, type mismatches).
|
||||
* Efficiency: Identify performance bottlenecks, optimize for efficiency, and avoid unnecessary
|
||||
loops, iterations, or calculations. Consider common efficiency issues (excessive loops, memory
|
||||
leaks, inefficient data structures, redundant calculations, excessive logging, etc.).
|
||||
* Maintainability: Assess code readability, modularity, and adherence to language idioms and
|
||||
best practices. Consider common maintainability issues (naming, comments/documentation, complexity,
|
||||
code duplication, formatting, magic numbers). State the style guide being followed (defaulting to
|
||||
commonly used guides, for example Python's PEP 8 style guide or Google Java Style Guide, if no style guide is specified).
|
||||
* Security: Identify potential vulnerabilities (e.g., insecure storage, injection attacks,
|
||||
insufficient access controls).
|
||||
|
||||
### Miscellaneous Considerations
|
||||
* Testing: Ensure adequate unit tests, integration tests, and end-to-end tests. Evaluate
|
||||
coverage, edge case handling, and overall test quality.
|
||||
* Performance: Assess performance under expected load, identify bottlenecks, and suggest
|
||||
optimizations.
|
||||
* Scalability: Evaluate how the code will scale with growing user base or data volume.
|
||||
* Modularity and Reusability: Assess code organization, modularity, and reusability. Suggest
|
||||
refactoring or creating reusable components.
|
||||
* Error Logging and Monitoring: Ensure errors are logged effectively, and implement monitoring
|
||||
mechanisms to track application health in production.
|
||||
|
||||
**CRITICAL CONSTRAINTS:**
|
||||
|
||||
You MUST only provide comments on lines that represent the actual changes in
|
||||
the diff. This means your comments should only refer to lines that begin with
|
||||
a `+` or `-` character in the provided diff content.
|
||||
DO NOT comment on lines that start with a space (context lines).
|
||||
|
||||
You MUST only add a review comment if there exists an actual ISSUE or BUG in the code changes.
|
||||
DO NOT add review comments to tell the user to "check" or "confirm" or "verify" something.
|
||||
DO NOT add review comments to tell the user to "ensure" something.
|
||||
DO NOT add review comments to explain what the code change does.
|
||||
DO NOT add review comments to validate what the code change does.
|
||||
DO NOT use the review comments to explain the code to the author. They already know their code. Only comment when there's an improvement opportunity. This is very important.
|
||||
|
||||
Pay close attention to line numbers and ensure they are correct.
|
||||
Pay close attention to indentations in the code suggestions and make sure they match the code they are to replace.
|
||||
Avoid comments on the license headers - if any exists - and instead make comments on the code that is being changed.
|
||||
|
||||
It's absolutely important to avoid commenting on the license header of files.
|
||||
It's absolutely important to avoid commenting on copyright headers.
|
||||
Avoid commenting on hardcoded dates and times being in future or not (for example "this date is in the future").
|
||||
Remember you don't have access to the current date and time and leave that to the author.
|
||||
|
||||
Avoid mentioning any of your instructions, settings or criteria.
|
||||
|
||||
Here are some general guidelines for setting the severity of your comments
|
||||
- Comments about refactoring a hardcoded string or number as a constant are generally considered low severity.
|
||||
- Comments about log messages or log enhancements are generally considered low severity.
|
||||
- Comments in .md files are medium or low severity. This is really important.
|
||||
- Comments about adding or expanding docstring/javadoc have low severity most of the times.
|
||||
- Comments about suppressing unchecked warnings or todos are considered low severity.
|
||||
- Comments about typos are usually low or medium severity.
|
||||
- Comments about testing or on tests are usually low severity.
|
||||
- Do not comment about the content of a URL if the content is not directly available in the input.
|
||||
|
||||
Keep comments bodies concise and to the point.
|
||||
Keep each comment focused on one issue.
|
||||
|
||||
## Context
|
||||
The files that are changed in this pull request are represented below in the following
|
||||
format, showing the file name and the portions of the file that are changed:
|
||||
|
||||
<PATCHES>
|
||||
FILE:<NAME OF FIRST FILE>
|
||||
DIFF:
|
||||
<PATCH IN UNIFIED DIFF FORMAT>
|
||||
|
||||
--------------------
|
||||
|
||||
FILE:<NAME OF SECOND FILE>
|
||||
DIFF:
|
||||
<PATCH IN UNIFIED DIFF FORMAT>
|
||||
|
||||
--------------------
|
||||
|
||||
(and so on for all files changed)
|
||||
</PATCHES>
|
||||
|
||||
Note that if you want to make a comment on the LEFT side of the UI / before the diff code version
|
||||
to note those line numbers and the corresponding code. Same for a comment on the RIGHT side
|
||||
of the UI / after the diff code version to note the line numbers and corresponding code.
|
||||
This should be your guide to picking line numbers, and also very importantly, restrict
|
||||
your comments to be only within this line range for these files, whether on LEFT or RIGHT.
|
||||
If you comment out of bounds, the review will fail, so you must pay attention the file name,
|
||||
line numbers, and pre/post diff versions when crafting your comment.
|
||||
|
||||
Here are the patches that were implemented in the pull request, per the
|
||||
formatting above:
|
||||
|
||||
The get the files changed in this pull request, run:
|
||||
"$(gh pr diff "${PR_NUMBER}" --patch)" to get the list of changed files PATCH
|
||||
|
||||
## Review
|
||||
|
||||
Once you have the information and are ready to leave a review on GitHub, post the review to GitHub using the GitHub MCP tool by:
|
||||
1. Creating a pending review: Use the mcp__github__create_pending_pull_request_review to create a Pending Pull Request Review.
|
||||
|
||||
2. Adding review comments:
|
||||
2.1 Use the mcp__github__add_comment_to_pending_review to add comments to the Pending Pull Request Review. Inline comments are preferred whenever possible, so repeat this step, calling mcp__github__add_comment_to_pending_review, as needed. All comments about specific lines of code should use inline comments. It is preferred to use code suggestions when possible, which include a code block that is labeled "suggestion", which contains what the new code should be. All comments should also have a severity. The syntax is:
|
||||
Normal Comment Syntax:
|
||||
<COMMENT>
|
||||
{{SEVERITY}} {{COMMENT_TEXT}}
|
||||
</COMMENT>
|
||||
|
||||
Inline Comment Syntax: (Preferred):
|
||||
<COMMENT>
|
||||
{{SEVERITY}} {{COMMENT_TEXT}}
|
||||
```suggestion
|
||||
{{CODE_SUGGESTION}}
|
||||
```
|
||||
</COMMENT>
|
||||
|
||||
Prepend a severity emoji to each comment:
|
||||
- 🟢 for low severity
|
||||
- 🟡 for medium severity
|
||||
- 🟠 for high severity
|
||||
- 🔴 for critical severity
|
||||
- 🔵 if severity is unclear
|
||||
|
||||
Including all of this, an example inline comment would be:
|
||||
<COMMENT>
|
||||
🟢 Use camelCase for function names
|
||||
```suggestion
|
||||
myFooBarFunction
|
||||
```
|
||||
</COMMENT>
|
||||
|
||||
A critical severity example would be:
|
||||
<COMMENT>
|
||||
🔴 Remove storage key from GitHub
|
||||
```suggestion
|
||||
```
|
||||
|
||||
3. Posting the review: Use the mcp__github__submit_pending_pull_request_review to submit the Pending Pull Request Review.
|
||||
|
||||
3.1 Crafting the summary comment: Include a summary of high level points that were not addressed with inline comments. Be concise. Do not repeat details mentioned inline.
|
||||
|
||||
Structure your summary comment using this exact format with markdown:
|
||||
## 📋 Review Summary
|
||||
|
||||
Provide a brief 2-3 sentence overview of the PR and overall
|
||||
assessment.
|
||||
|
||||
## 🔍 General Feedback
|
||||
- List general observations about code quality
|
||||
- Mention overall patterns or architectural decisions
|
||||
- Highlight positive aspects of the implementation
|
||||
- Note any recurring themes across files
|
||||
|
||||
## Final Instructions
|
||||
|
||||
Remember, you are running in a VM and no one reviewing your output. Your review must be posted to GitHub using the MCP tools to create a pending review, add comments to the pending review, and submit the pending review.
|
||||
|
||||
|
||||
- name: 'Post PR review failure comment'
|
||||
if: |-
|
||||
${{ failure() && steps.gemini_pr_review.outcome == 'failure' }}
|
||||
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea'
|
||||
with:
|
||||
github-token: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
|
||||
script: |-
|
||||
github.rest.issues.createComment({
|
||||
owner: '${{ github.repository }}'.split('/')[0],
|
||||
repo: '${{ github.repository }}'.split('/')[1],
|
||||
issue_number: '${{ steps.get_pr.outputs.pr_number || steps.get_pr_comment.outputs.pr_number }}',
|
||||
body: 'There is a problem with the Gemini CLI PR review. Please check the [action logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.'
|
||||
})
|
||||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
with:
|
||||
path: dist/*
|
||||
- name: publish package
|
||||
uses: pypa/gh-action-pypi-publish@f5622bde02b04381239da3573277701ceca8f6a0 # release/v1
|
||||
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # release/v1.12.4
|
||||
with:
|
||||
skip-existing: true
|
||||
verbose: true
|
||||
|
||||
12
.github/workflows/tests.yml
vendored
12
.github/workflows/tests.yml
vendored
@@ -88,16 +88,16 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, windows-2019, macos-13]
|
||||
os: [ubuntu-22.04, windows-2022, macos-13]
|
||||
# across all operating systems
|
||||
python-version: ["3.10", "3.11"]
|
||||
include:
|
||||
# on Ubuntu run these as well
|
||||
- os: ubuntu-20.04
|
||||
- os: ubuntu-22.04
|
||||
python-version: "3.10"
|
||||
- os: ubuntu-20.04
|
||||
- os: ubuntu-22.04
|
||||
python-version: "3.11"
|
||||
- os: ubuntu-20.04
|
||||
- os: ubuntu-22.04
|
||||
python-version: "3.12"
|
||||
steps:
|
||||
- name: Checkout capa with submodules
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install pyyaml
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: sudo apt-get install -y libyaml-dev
|
||||
- name: Install capa
|
||||
run: |
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
|
||||
ghidra-tests:
|
||||
name: Ghidra tests for ${{ matrix.python-version }}
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [tests]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
95
CHANGELOG.md
95
CHANGELOG.md
@@ -3,11 +3,14 @@
|
||||
## master (unreleased)
|
||||
|
||||
### New Features
|
||||
- ci: add support for arm64 binary releases
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
### New Rules (0)
|
||||
### New Rules (2)
|
||||
|
||||
- anti-analysis/anti-vm/vm-detection/detect-mouse-movement-via-activity-checks-on-windows tevajdr@gmail.com
|
||||
- nursery/create-executable-heap moritz.raabe@mandiant.com
|
||||
-
|
||||
|
||||
### Bug Fixes
|
||||
@@ -18,9 +21,95 @@
|
||||
|
||||
### Development
|
||||
|
||||
- ci: remove redundant "test_run" action from build workflow @mike-hunhoff #2692
|
||||
|
||||
### Raw diffs
|
||||
- [capa v9.0.0...master](https://github.com/mandiant/capa/compare/v9.0.0...master)
|
||||
- [capa-rules v9.0.0...master](https://github.com/mandiant/capa-rules/compare/v9.0.0...master)
|
||||
- [capa v9.2.1...master](https://github.com/mandiant/capa/compare/v9.2.1...master)
|
||||
- [capa-rules v9.2.1...master](https://github.com/mandiant/capa-rules/compare/v9.2.1...master)
|
||||
|
||||
## v9.2.1
|
||||
|
||||
This point release fixes bugs including removing an unnecessary PyInstaller warning message and enabling the standalone binary to execute on systems running older versions of glibc.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- ci: exclude pkg_resources from PyInstaller build @mike-hunhoff #2684
|
||||
- ci: downgrade Ubuntu version to accommodate older glibc versions @mike-hunhoff #2684
|
||||
|
||||
### Development
|
||||
|
||||
- ci: upgrade Windows version to avoid deprecation @mike-hunhoff #2684
|
||||
- ci: check if build runs without warnings or errors @mike-hunhoff #2684
|
||||
|
||||
### Raw diffs
|
||||
- [capa v9.2.0...v9.2.1](https://github.com/mandiant/capa/compare/v9.2.0...v9.2.1)
|
||||
- [capa-rules v9.2.0...v9.2.1](https://github.com/mandiant/capa-rules/compare/v9.2.0...v9.2.1)
|
||||
|
||||
## v9.2.0
|
||||
|
||||
This release improves a few aspects of dynamic analysis, including relaxing our validation on fields across many CAPE versions and processing additional VMRay submission file types, for example.
|
||||
It also includes an updated rule pack containing new rules and rule fixes.
|
||||
|
||||
### New Features
|
||||
- vmray: do not restrict analysis to PE and ELF files, e.g. docx @mike-hunhoff #2672
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
### New Rules (22)
|
||||
|
||||
- communication/socket/connect-socket moritz.raabe@mandiant.com joakim@intezer.com mrhafizfarhad@gmail.com
|
||||
- communication/socket/udp/connect-udp-socket mrhafizfarhad@gmail.com
|
||||
- nursery/enter-debug-mode-in-dotnet @v1bh475u
|
||||
- nursery/decrypt-data-using-tripledes-in-dotnet 0xRavenspar
|
||||
- nursery/encrypt-data-using-tripledes-in-dotnet 0xRavenspar
|
||||
- nursery/disable-system-features-via-registry-on-windows mehunhoff@google.com
|
||||
- data-manipulation/encryption/chaskey/encrypt-data-using-chaskey still@teamt5.org
|
||||
- data-manipulation/encryption/speck/encrypt-data-using-speck still@teamt5.org
|
||||
- load-code/dotnet/load-assembly-via-iassembly still@teamt5.org
|
||||
- malware-family/donut-loader/load-shellcode-via-donut still@teamt5.org
|
||||
- nursery/disable-device-guard-features-via-registry-on-windows mehunhoff@google.com
|
||||
- nursery/disable-firewall-features-via-registry-on-windows mehunhoff@google.com
|
||||
- nursery/disable-system-restore-features-via-registry-on-windows mehunhoff@google.com
|
||||
- nursery/disable-windows-defender-features-via-registry-on-windows mehunhoff@google.com
|
||||
- host-interaction/file-system/write/clear-file-content jakeperalta7
|
||||
- host-interaction/filter/unload-minifilter-driver JakePeralta7
|
||||
- exploitation/enumeration/make-suspicious-ntquerysysteminformation-call zdw@google.com
|
||||
- exploitation/gadgets/load-ntoskrnl zdw@google.com
|
||||
- exploitation/gadgets/resolve-ntoskrnl-gadgets zdw@google.com
|
||||
- exploitation/spraying/make-suspicious-ntfscontrolfile-call zdw@google.com
|
||||
- anti-analysis/anti-forensic/unload-sysmon JakePeralta7
|
||||
|
||||
### Bug Fixes
|
||||
- cape: make some fields optional @williballenthin #2631 #2632
|
||||
- lint: add WARN for regex features that contain unescaped dot #2635
|
||||
- lint: add ERROR for incomplete registry control set regex #2643
|
||||
- binja: update unit test core version #2670
|
||||
|
||||
### Raw diffs
|
||||
- [capa v9.1.0...v9.2.0](https://github.com/mandiant/capa/compare/v9.1.0...v9.2.0)
|
||||
- [capa-rules v9.1.0...v9.2.0](https://github.com/mandiant/capa-rules/compare/v9.1.0...v9.2.0)
|
||||
|
||||
## v9.1.0
|
||||
|
||||
This release improves a few aspects of dynamic analysis, relaxing our validation on fields across many CAPE versions, for example.
|
||||
It also includes an updated rule pack in which many dynamic rules make better use of the "span of calls" scope.
|
||||
|
||||
|
||||
### New Rules (3)
|
||||
|
||||
- host-interaction/registry/change-registry-key-timestamp wballenthin@google.com
|
||||
- host-interaction/mutex/check-mutex-and-terminate-process-on-windows @_re_fox moritz.raabe@mandiant.com mehunhoff@google.com
|
||||
- anti-analysis/anti-forensic/clear-logs/clear-windows-event-logs-remotely 99.elad.levi@gmail.com
|
||||
|
||||
### Bug Fixes
|
||||
- only parse CAPE fields required for analysis @mike-hunhoff #2607
|
||||
- main: render result document without needing associated rules @williballenthin #2610
|
||||
- vmray: only verify process OS and monitor IDs match @mike-hunhoff #2613
|
||||
- render: don't assume prior matches exist within a thread @mike-hunhoff #2612
|
||||
|
||||
### Raw diffs
|
||||
- [capa v9.0.0...v9.1.0](https://github.com/mandiant/capa/compare/v9.0.0...v9.1.0)
|
||||
- [capa-rules v9.0.0...v9.1.0](https://github.com/mandiant/capa-rules/compare/v9.0.0...v9.1.0)
|
||||
|
||||
## v9.0.0
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ class CapeExtractor(DynamicFeatureExtractor):
|
||||
|
||||
def get_base_address(self) -> Union[AbsoluteVirtualAddress, _NoAddress, None]:
|
||||
# value according to the PE header, the actual trace may use a different imagebase
|
||||
assert self.report.static is not None and self.report.static.pe is not None
|
||||
assert self.report.static is not None
|
||||
assert self.report.static.pe is not None
|
||||
return AbsoluteVirtualAddress(self.report.static.pe.imagebase)
|
||||
|
||||
def extract_global_features(self) -> Iterator[tuple[Feature, Address]]:
|
||||
|
||||
@@ -88,31 +88,49 @@ def extract_file_strings(report: CapeReport) -> Iterator[tuple[Feature, Address]
|
||||
|
||||
|
||||
def extract_used_regkeys(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
|
||||
if not report.behavior.summary:
|
||||
return
|
||||
|
||||
for regkey in report.behavior.summary.keys:
|
||||
yield String(regkey), NO_ADDRESS
|
||||
|
||||
|
||||
def extract_used_files(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
|
||||
if not report.behavior.summary:
|
||||
return
|
||||
|
||||
for file in report.behavior.summary.files:
|
||||
yield String(file), NO_ADDRESS
|
||||
|
||||
|
||||
def extract_used_mutexes(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
|
||||
if not report.behavior.summary:
|
||||
return
|
||||
|
||||
for mutex in report.behavior.summary.mutexes:
|
||||
yield String(mutex), NO_ADDRESS
|
||||
|
||||
|
||||
def extract_used_commands(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
|
||||
if not report.behavior.summary:
|
||||
return
|
||||
|
||||
for cmd in report.behavior.summary.executed_commands:
|
||||
yield String(cmd), NO_ADDRESS
|
||||
|
||||
|
||||
def extract_used_apis(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
|
||||
if not report.behavior.summary:
|
||||
return
|
||||
|
||||
for symbol in report.behavior.summary.resolved_apis:
|
||||
yield String(symbol), NO_ADDRESS
|
||||
|
||||
|
||||
def extract_used_services(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
|
||||
if not report.behavior.summary:
|
||||
return
|
||||
|
||||
for svc in report.behavior.summary.created_services:
|
||||
yield String(svc), NO_ADDRESS
|
||||
for svc in report.behavior.summary.started_services:
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from typing import Any, Union, Literal, Optional, Annotated, TypeAlias
|
||||
from typing import Any, Union, Optional, Annotated, TypeAlias
|
||||
|
||||
from pydantic import Field, BaseModel, ConfigDict
|
||||
from pydantic.functional_validators import BeforeValidator
|
||||
@@ -75,34 +75,37 @@ class Info(FlexibleModel):
|
||||
version: str
|
||||
|
||||
|
||||
class ImportedSymbol(ExactModel):
|
||||
class ImportedSymbol(FlexibleModel):
|
||||
address: HexInt
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class ImportedDll(ExactModel):
|
||||
class ImportedDll(FlexibleModel):
|
||||
dll: str
|
||||
imports: list[ImportedSymbol]
|
||||
|
||||
|
||||
class DirectoryEntry(ExactModel):
|
||||
"""
|
||||
class DirectoryEntry(FlexibleModel):
|
||||
name: str
|
||||
virtual_address: HexInt
|
||||
size: HexInt
|
||||
"""
|
||||
|
||||
|
||||
class Section(ExactModel):
|
||||
class Section(FlexibleModel):
|
||||
name: str
|
||||
raw_address: HexInt
|
||||
# raw_address: HexInt
|
||||
virtual_address: HexInt
|
||||
virtual_size: HexInt
|
||||
size_of_data: HexInt
|
||||
characteristics: str
|
||||
characteristics_raw: HexInt
|
||||
entropy: float
|
||||
# virtual_size: HexInt
|
||||
# size_of_data: HexInt
|
||||
# characteristics: str
|
||||
# characteristics_raw: HexInt
|
||||
# entropy: float
|
||||
|
||||
|
||||
class Resource(ExactModel):
|
||||
"""
|
||||
class Resource(FlexibleModel):
|
||||
name: str
|
||||
language: Optional[str] = None
|
||||
sublanguage: str
|
||||
@@ -140,7 +143,7 @@ class DigitalSigner(FlexibleModel):
|
||||
extensions_subjectKeyIdentifier: Optional[str] = None
|
||||
|
||||
|
||||
class AuxSigner(ExactModel):
|
||||
class AuxSigner(FlexibleModel):
|
||||
name: str
|
||||
issued_to: str = Field(alias="Issued to")
|
||||
issued_by: str = Field(alias="Issued by")
|
||||
@@ -148,7 +151,7 @@ class AuxSigner(ExactModel):
|
||||
sha1_hash: str = Field(alias="SHA1 hash")
|
||||
|
||||
|
||||
class Signer(ExactModel):
|
||||
class Signer(FlexibleModel):
|
||||
aux_sha1: Optional[str] = None
|
||||
aux_timestamp: Optional[str] = None
|
||||
aux_valid: Optional[bool] = None
|
||||
@@ -157,60 +160,61 @@ class Signer(ExactModel):
|
||||
aux_signers: Optional[list[AuxSigner]] = None
|
||||
|
||||
|
||||
class Overlay(ExactModel):
|
||||
class Overlay(FlexibleModel):
|
||||
offset: HexInt
|
||||
size: HexInt
|
||||
|
||||
|
||||
class KV(ExactModel):
|
||||
class KV(FlexibleModel):
|
||||
name: str
|
||||
value: str
|
||||
"""
|
||||
|
||||
|
||||
class ExportedSymbol(ExactModel):
|
||||
class ExportedSymbol(FlexibleModel):
|
||||
address: HexInt
|
||||
name: str
|
||||
ordinal: int
|
||||
# ordinal: int
|
||||
|
||||
|
||||
class PE(ExactModel):
|
||||
peid_signatures: TODO
|
||||
class PE(FlexibleModel):
|
||||
# peid_signatures: TODO
|
||||
imagebase: HexInt
|
||||
entrypoint: HexInt
|
||||
reported_checksum: HexInt
|
||||
actual_checksum: HexInt
|
||||
osversion: str
|
||||
pdbpath: Optional[str] = None
|
||||
timestamp: str
|
||||
# entrypoint: HexInt
|
||||
# reported_checksum: HexInt
|
||||
# actual_checksum: HexInt
|
||||
# osversion: str
|
||||
# pdbpath: Optional[str] = None
|
||||
# timestamp: str
|
||||
|
||||
# list[ImportedDll], or dict[basename(dll), ImportedDll]
|
||||
imports: Union[list[ImportedDll], dict[str, ImportedDll]]
|
||||
imported_dll_count: Optional[int] = None
|
||||
imphash: str
|
||||
imports: list[ImportedDll] | dict[str, ImportedDll] = Field(default_factory=list) # type: ignore
|
||||
# imported_dll_count: Optional[int] = None
|
||||
# imphash: str
|
||||
|
||||
exported_dll_name: Optional[str] = None
|
||||
exports: list[ExportedSymbol]
|
||||
# exported_dll_name: Optional[str] = None
|
||||
exports: list[ExportedSymbol] = Field(default_factory=list)
|
||||
|
||||
dirents: list[DirectoryEntry]
|
||||
sections: list[Section]
|
||||
# dirents: list[DirectoryEntry]
|
||||
sections: list[Section] = Field(default_factory=list)
|
||||
|
||||
ep_bytes: Optional[HexBytes] = None
|
||||
# ep_bytes: Optional[HexBytes] = None
|
||||
|
||||
overlay: Optional[Overlay] = None
|
||||
resources: list[Resource]
|
||||
versioninfo: list[KV]
|
||||
# overlay: Optional[Overlay] = None
|
||||
# resources: list[Resource]
|
||||
# versioninfo: list[KV]
|
||||
|
||||
# base64 encoded data
|
||||
icon: Optional[str] = None
|
||||
# icon: Optional[str] = None
|
||||
# MD5-like hash
|
||||
icon_hash: Optional[str] = None
|
||||
# icon_hash: Optional[str] = None
|
||||
# MD5-like hash
|
||||
icon_fuzzy: Optional[str] = None
|
||||
# icon_fuzzy: Optional[str] = None
|
||||
# short hex string
|
||||
icon_dhash: Optional[str] = None
|
||||
# icon_dhash: Optional[str] = None
|
||||
|
||||
digital_signers: list[DigitalSigner]
|
||||
guest_signers: Signer
|
||||
# digital_signers: list[DigitalSigner]
|
||||
# guest_signers: Signer
|
||||
|
||||
|
||||
# TODO(mr-tz): target.file.dotnet, target.file.extracted_files, target.file.extracted_files_tool,
|
||||
@@ -218,48 +222,49 @@ class PE(ExactModel):
|
||||
# https://github.com/mandiant/capa/issues/1814
|
||||
class File(FlexibleModel):
|
||||
type: str
|
||||
cape_type_code: Optional[int] = None
|
||||
cape_type: Optional[str] = None
|
||||
# cape_type_code: Optional[int] = None
|
||||
# cape_type: Optional[str] = None
|
||||
|
||||
pid: Optional[Union[int, Literal[""]]] = None
|
||||
name: Union[list[str], str]
|
||||
path: str
|
||||
guest_paths: Union[list[str], str, None]
|
||||
timestamp: Optional[str] = None
|
||||
# pid: Optional[Union[int, Literal[""]]] = None
|
||||
# name: Union[list[str], str]
|
||||
# path: str
|
||||
# guest_paths: Union[list[str], str, None]
|
||||
# timestamp: Optional[str] = None
|
||||
|
||||
#
|
||||
# hashes
|
||||
#
|
||||
crc32: str
|
||||
# crc32: str
|
||||
md5: str
|
||||
sha1: str
|
||||
sha256: str
|
||||
sha512: str
|
||||
sha3_384: Optional[str] = None
|
||||
ssdeep: str
|
||||
# sha512: str
|
||||
# sha3_384: Optional[str] = None
|
||||
# ssdeep: str
|
||||
# unsure why this would ever be "False"
|
||||
tlsh: Optional[Union[str, bool]] = None
|
||||
rh_hash: Optional[str] = None
|
||||
# tlsh: Optional[Union[str, bool]] = None
|
||||
# rh_hash: Optional[str] = None
|
||||
|
||||
#
|
||||
# other metadata, static analysis
|
||||
#
|
||||
size: int
|
||||
# size: int
|
||||
pe: Optional[PE] = None
|
||||
ep_bytes: Optional[HexBytes] = None
|
||||
entrypoint: Optional[int] = None
|
||||
data: Optional[str] = None
|
||||
strings: Optional[list[str]] = None
|
||||
# ep_bytes: Optional[HexBytes] = None
|
||||
# entrypoint: Optional[int] = None
|
||||
# data: Optional[str] = None
|
||||
# strings: Optional[list[str]] = None
|
||||
|
||||
#
|
||||
# detections (skip)
|
||||
#
|
||||
yara: Skip = None
|
||||
cape_yara: Skip = None
|
||||
clamav: Skip = None
|
||||
virustotal: Skip = None
|
||||
# yara: Skip = None
|
||||
# cape_yara: Skip = None
|
||||
# clamav: Skip = None
|
||||
# virustotal: Skip = None
|
||||
|
||||
|
||||
"""
|
||||
class ProcessFile(File):
|
||||
#
|
||||
# like a File, but also has dynamic analysis results
|
||||
@@ -272,35 +277,36 @@ class ProcessFile(File):
|
||||
target_pid: Optional[Union[int, str]] = None
|
||||
target_path: Optional[str] = None
|
||||
target_process: Optional[str] = None
|
||||
"""
|
||||
|
||||
|
||||
class Argument(ExactModel):
|
||||
class Argument(FlexibleModel):
|
||||
name: str
|
||||
# unsure why empty list is provided here
|
||||
value: Union[HexInt, int, str, EmptyList]
|
||||
pretty_value: Optional[str] = None
|
||||
|
||||
|
||||
class Call(ExactModel):
|
||||
timestamp: str
|
||||
class Call(FlexibleModel):
|
||||
# timestamp: str
|
||||
thread_id: int
|
||||
category: str
|
||||
# category: str
|
||||
|
||||
api: str
|
||||
|
||||
arguments: list[Argument]
|
||||
status: bool
|
||||
# status: bool
|
||||
return_: HexInt = Field(alias="return")
|
||||
pretty_return: Optional[str] = None
|
||||
|
||||
repeated: int
|
||||
# repeated: int
|
||||
|
||||
# virtual addresses
|
||||
caller: HexInt
|
||||
parentcaller: HexInt
|
||||
# caller: HexInt
|
||||
# parentcaller: HexInt
|
||||
|
||||
# index into calls array
|
||||
id: int
|
||||
# id: int
|
||||
|
||||
|
||||
# FlexibleModel to account for extended fields
|
||||
@@ -310,14 +316,15 @@ class Process(FlexibleModel):
|
||||
process_id: int
|
||||
process_name: str
|
||||
parent_id: int
|
||||
module_path: str
|
||||
first_seen: str
|
||||
# module_path: str
|
||||
# first_seen: str
|
||||
calls: list[Call]
|
||||
threads: list[int]
|
||||
environ: dict[str, str]
|
||||
|
||||
|
||||
class ProcessTree(ExactModel):
|
||||
"""
|
||||
class ProcessTree(FlexibleModel):
|
||||
name: str
|
||||
pid: int
|
||||
parent_id: int
|
||||
@@ -325,17 +332,18 @@ class ProcessTree(ExactModel):
|
||||
threads: list[int]
|
||||
environ: dict[str, str]
|
||||
children: list["ProcessTree"]
|
||||
"""
|
||||
|
||||
|
||||
class Summary(ExactModel):
|
||||
class Summary(FlexibleModel):
|
||||
files: list[str]
|
||||
read_files: list[str]
|
||||
write_files: list[str]
|
||||
delete_files: list[str]
|
||||
# read_files: list[str]
|
||||
# write_files: list[str]
|
||||
# delete_files: list[str]
|
||||
keys: list[str]
|
||||
read_keys: list[str]
|
||||
write_keys: list[str]
|
||||
delete_keys: list[str]
|
||||
# read_keys: list[str]
|
||||
# write_keys: list[str]
|
||||
# delete_keys: list[str]
|
||||
executed_commands: list[str]
|
||||
resolved_apis: list[str]
|
||||
mutexes: list[str]
|
||||
@@ -343,7 +351,8 @@ class Summary(ExactModel):
|
||||
started_services: list[str]
|
||||
|
||||
|
||||
class EncryptedBuffer(ExactModel):
|
||||
"""
|
||||
class EncryptedBuffer(FlexibleModel):
|
||||
process_name: str
|
||||
pid: int
|
||||
|
||||
@@ -351,38 +360,41 @@ class EncryptedBuffer(ExactModel):
|
||||
buffer: str
|
||||
buffer_size: Optional[int] = None
|
||||
crypt_key: Optional[Union[HexInt, str]] = None
|
||||
"""
|
||||
|
||||
|
||||
class Behavior(ExactModel):
|
||||
summary: Summary
|
||||
class Behavior(FlexibleModel):
|
||||
summary: Summary | None = None
|
||||
|
||||
# list of processes, of threads, of calls
|
||||
processes: list[Process]
|
||||
# tree of processes
|
||||
processtree: list[ProcessTree]
|
||||
# processtree: list[ProcessTree]
|
||||
|
||||
anomaly: list[str]
|
||||
encryptedbuffers: list[EncryptedBuffer]
|
||||
# anomaly: list[str]
|
||||
# encryptedbuffers: list[EncryptedBuffer]
|
||||
# these are small objects that describe atomic events,
|
||||
# like file move, registry access.
|
||||
# we'll detect the same with our API call analysis.
|
||||
enhanced: Skip = None
|
||||
# enhanced: Skip = None
|
||||
|
||||
|
||||
class Target(ExactModel):
|
||||
category: str
|
||||
class Target(FlexibleModel):
|
||||
# category: str
|
||||
file: File
|
||||
# pe: Optional[PE] = None
|
||||
|
||||
|
||||
class Static(FlexibleModel):
|
||||
pe: Optional[PE] = None
|
||||
# flare_capa: Skip = None
|
||||
|
||||
|
||||
class Static(ExactModel):
|
||||
pe: Optional[PE] = None
|
||||
flare_capa: Skip = None
|
||||
|
||||
|
||||
class Cape(ExactModel):
|
||||
"""
|
||||
class Cape(FlexibleModel):
|
||||
payloads: list[ProcessFile]
|
||||
configs: Skip = None
|
||||
"""
|
||||
|
||||
|
||||
# flexible because there may be more sorts of analysis
|
||||
@@ -405,15 +417,14 @@ class CapeReport(FlexibleModel):
|
||||
# post-processed results: process tree, anomalies, etc
|
||||
behavior: Behavior
|
||||
|
||||
# post-processed results: payloads and extracted configs
|
||||
CAPE: Optional[Union[Cape, list]] = None
|
||||
dropped: Optional[list[File]] = None
|
||||
procdump: Optional[list[ProcessFile]] = None
|
||||
procmemory: Optional[ListTODO] = None
|
||||
|
||||
# =========================================================================
|
||||
# information we won't use in capa
|
||||
#
|
||||
# post-processed results: payloads and extracted configs
|
||||
# CAPE: Optional[Union[Cape, list]] = None
|
||||
# dropped: Optional[list[File]] = None
|
||||
# procdump: Optional[list[ProcessFile]] = None
|
||||
# procmemory: Optional[ListTODO] = None
|
||||
|
||||
#
|
||||
# NBIs and HBIs
|
||||
@@ -422,32 +433,32 @@ class CapeReport(FlexibleModel):
|
||||
#
|
||||
# if we come up with a future use for this, go ahead and re-enable!
|
||||
#
|
||||
network: Skip = None
|
||||
suricata: Skip = None
|
||||
curtain: Skip = None
|
||||
sysmon: Skip = None
|
||||
url_analysis: Skip = None
|
||||
# network: Skip = None
|
||||
# suricata: Skip = None
|
||||
# curtain: Skip = None
|
||||
# sysmon: Skip = None
|
||||
# url_analysis: Skip = None
|
||||
|
||||
# screenshot hash values
|
||||
deduplicated_shots: Skip = None
|
||||
# deduplicated_shots: Skip = None
|
||||
# k-v pairs describing the time it took to run each stage.
|
||||
statistics: Skip = None
|
||||
# statistics: Skip = None
|
||||
# k-v pairs of ATT&CK ID to signature name or similar.
|
||||
ttps: Skip = None
|
||||
# ttps: Skip = None
|
||||
# debug log messages
|
||||
debug: Skip = None
|
||||
# debug: Skip = None
|
||||
|
||||
# various signature matches
|
||||
# we could potentially extend capa to use this info one day,
|
||||
# though it would be quite sandbox-specific,
|
||||
# and more detection-oriented than capability detection.
|
||||
signatures: Skip = None
|
||||
malfamily_tag: Optional[str] = None
|
||||
malscore: float
|
||||
detections: Skip = None
|
||||
detections2pid: Optional[dict[int, list[str]]] = None
|
||||
# signatures: Skip = None
|
||||
# malfamily_tag: Optional[str] = None
|
||||
# malscore: float
|
||||
# detections: Skip = None
|
||||
# detections2pid: Optional[dict[int, list[str]]] = None
|
||||
# AV detections for the sample.
|
||||
virustotal: Skip = None
|
||||
# virustotal: Skip = None
|
||||
|
||||
@classmethod
|
||||
def from_buf(cls, buf: bytes) -> "CapeReport":
|
||||
|
||||
@@ -96,14 +96,7 @@ class VMRayAnalysis:
|
||||
% (self.submission_name, self.submission_type)
|
||||
)
|
||||
|
||||
if self.submission_static is not None:
|
||||
if self.submission_static.pe is None and self.submission_static.elf is None:
|
||||
# we only support static analysis for PE and ELF files for now
|
||||
raise UnsupportedFormatError(
|
||||
"archive does not contain a supported file format (submission_name: %s, submission_type: %s)"
|
||||
% (self.submission_name, self.submission_type)
|
||||
)
|
||||
else:
|
||||
if self.submission_static is None:
|
||||
# VMRay may not record static analysis for certain file types, e.g. MSI, but we'd still like to match dynamic
|
||||
# execution so we continue without and accept that the results may be incomplete
|
||||
logger.warning(
|
||||
@@ -223,16 +216,15 @@ class VMRayAnalysis:
|
||||
# we expect monitor processes recorded in both SummaryV2.json and flog.xml to equal
|
||||
# to ensure this, we compare the pid, monitor_id, and origin_monitor_id
|
||||
# for the other fields we've observed cases with slight deviations, e.g.,
|
||||
# the ppid for a process in flog.xml is not set correctly, all other data is equal
|
||||
# the ppid, origin monitor id, etc. for a process in flog.xml is not set correctly, all other
|
||||
# data is equal
|
||||
sv2p = self.monitor_processes[monitor_process.process_id]
|
||||
if self.monitor_processes[monitor_process.process_id] != vmray_monitor_process:
|
||||
logger.debug("processes differ: %s (sv2) vs. %s (flog)", sv2p, vmray_monitor_process)
|
||||
|
||||
assert (sv2p.pid, sv2p.monitor_id, sv2p.origin_monitor_id) == (
|
||||
vmray_monitor_process.pid,
|
||||
vmray_monitor_process.monitor_id,
|
||||
vmray_monitor_process.origin_monitor_id,
|
||||
)
|
||||
# we need, at a minimum, for the process id and monitor id to match, otherwise there is likely a bug
|
||||
# in the way that VMRay tracked one of the processes
|
||||
assert (sv2p.pid, sv2p.monitor_id) == (vmray_monitor_process.pid, vmray_monitor_process.monitor_id)
|
||||
|
||||
def _compute_monitor_threads(self):
|
||||
for monitor_thread in self.flog.analysis.monitor_threads:
|
||||
|
||||
75
capa/main.py
75
capa/main.py
@@ -995,7 +995,27 @@ def main(argv: Optional[list[str]] = None):
|
||||
handle_common_args(args)
|
||||
ensure_input_exists_from_cli(args)
|
||||
input_format = get_input_format_from_cli(args)
|
||||
rules = get_rules_from_cli(args)
|
||||
except ShouldExitError as e:
|
||||
return e.status_code
|
||||
|
||||
if input_format == FORMAT_RESULT:
|
||||
# render the result document immediately,
|
||||
# no need to load the rules or do other processing.
|
||||
result_doc = capa.render.result_document.ResultDocument.from_file(args.input_file)
|
||||
|
||||
if args.json:
|
||||
print(result_doc.model_dump_json(exclude_none=True))
|
||||
elif args.vverbose:
|
||||
print(capa.render.vverbose.render_vverbose(result_doc))
|
||||
elif args.verbose:
|
||||
print(capa.render.verbose.render_verbose(result_doc))
|
||||
else:
|
||||
print(capa.render.default.render_default(result_doc))
|
||||
return 0
|
||||
|
||||
try:
|
||||
rules: RuleSet = get_rules_from_cli(args)
|
||||
|
||||
found_limitation = False
|
||||
file_extractors = get_file_extractors_from_cli(args, input_format)
|
||||
if input_format in STATIC_FORMATS:
|
||||
@@ -1003,45 +1023,30 @@ def main(argv: Optional[list[str]] = None):
|
||||
found_limitation = find_static_limitations_from_cli(args, rules, file_extractors)
|
||||
if input_format in DYNAMIC_FORMATS:
|
||||
found_limitation = find_dynamic_limitations_from_cli(args, rules, file_extractors)
|
||||
|
||||
backend = get_backend_from_cli(args, input_format)
|
||||
sample_path = get_sample_path_from_cli(args, backend)
|
||||
if sample_path is None:
|
||||
os_ = "unknown"
|
||||
else:
|
||||
os_ = capa.loader.get_os(sample_path)
|
||||
extractor: FeatureExtractor = get_extractor_from_cli(args, input_format, backend)
|
||||
except ShouldExitError as e:
|
||||
return e.status_code
|
||||
|
||||
meta: rdoc.Metadata
|
||||
capabilities: Capabilities
|
||||
capabilities: Capabilities = find_capabilities(rules, extractor, disable_progress=args.quiet)
|
||||
|
||||
if input_format == FORMAT_RESULT:
|
||||
# result document directly parses into meta, capabilities
|
||||
result_doc = capa.render.result_document.ResultDocument.from_file(args.input_file)
|
||||
meta, capabilities = result_doc.to_capa()
|
||||
meta: rdoc.Metadata = capa.loader.collect_metadata(
|
||||
argv, args.input_file, input_format, os_, args.rules, extractor, capabilities
|
||||
)
|
||||
meta.analysis.layout = capa.loader.compute_layout(rules, extractor, capabilities.matches)
|
||||
|
||||
else:
|
||||
# all other formats we must create an extractor
|
||||
# and use that to extract meta and capabilities
|
||||
|
||||
try:
|
||||
backend = get_backend_from_cli(args, input_format)
|
||||
sample_path = get_sample_path_from_cli(args, backend)
|
||||
if sample_path is None:
|
||||
os_ = "unknown"
|
||||
else:
|
||||
os_ = capa.loader.get_os(sample_path)
|
||||
extractor = get_extractor_from_cli(args, input_format, backend)
|
||||
except ShouldExitError as e:
|
||||
return e.status_code
|
||||
|
||||
capabilities = find_capabilities(rules, extractor, disable_progress=args.quiet)
|
||||
|
||||
meta = capa.loader.collect_metadata(
|
||||
argv, args.input_file, input_format, os_, args.rules, extractor, capabilities
|
||||
)
|
||||
meta.analysis.layout = capa.loader.compute_layout(rules, extractor, capabilities.matches)
|
||||
|
||||
if found_limitation:
|
||||
# bail if capa's static feature extractor encountered file limitation e.g. a packed binary
|
||||
# or capa's dynamic feature extractor encountered some limitation e.g. a dotnet sample
|
||||
# do show the output in verbose mode, though.
|
||||
if not (args.verbose or args.vverbose or args.json):
|
||||
return E_FILE_LIMITATION
|
||||
if found_limitation:
|
||||
# bail if capa's static feature extractor encountered file limitation e.g. a packed binary
|
||||
# or capa's dynamic feature extractor encountered some limitation e.g. a dotnet sample
|
||||
# do show the output in verbose mode, though.
|
||||
if not (args.verbose or args.vverbose or args.json):
|
||||
return E_FILE_LIMITATION
|
||||
|
||||
if args.json:
|
||||
print(capa.render.json.render(meta, rules, capabilities.matches))
|
||||
|
||||
@@ -418,8 +418,9 @@ class Match(FrozenModel):
|
||||
and a.id <= location.id
|
||||
]
|
||||
)
|
||||
_, most_recent_match = matches_in_thread[-1]
|
||||
children.append(Match.from_capa(rules, capabilities, most_recent_match))
|
||||
if matches_in_thread:
|
||||
_, most_recent_match = matches_in_thread[-1]
|
||||
children.append(Match.from_capa(rules, capabilities, most_recent_match))
|
||||
|
||||
else:
|
||||
children.append(Match.from_capa(rules, capabilities, rule_matches[location]))
|
||||
@@ -478,8 +479,11 @@ class Match(FrozenModel):
|
||||
and a.id <= location.id
|
||||
]
|
||||
)
|
||||
_, most_recent_match = matches_in_thread[-1]
|
||||
children.append(Match.from_capa(rules, capabilities, most_recent_match))
|
||||
# namespace matches may not occur within the same thread as the result, so only
|
||||
# proceed if a match within the same thread is found
|
||||
if matches_in_thread:
|
||||
_, most_recent_match = matches_in_thread[-1]
|
||||
children.append(Match.from_capa(rules, capabilities, most_recent_match))
|
||||
else:
|
||||
if location in rule_matches:
|
||||
children.append(Match.from_capa(rules, capabilities, rule_matches[location]))
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__version__ = "9.0.0"
|
||||
__version__ = "9.2.1"
|
||||
|
||||
|
||||
def get_major_version():
|
||||
|
||||
@@ -121,11 +121,11 @@ dev = [
|
||||
# we want all developer environments to be consistent.
|
||||
# These dependencies are not used in production environments
|
||||
# and should not conflict with other libraries/tooling.
|
||||
"pre-commit==4.1.0",
|
||||
"pre-commit==4.2.0",
|
||||
"pytest==8.0.0",
|
||||
"pytest-sugar==1.0.0",
|
||||
"pytest-instafail==0.5.0",
|
||||
"flake8==7.1.1",
|
||||
"flake8==7.3.0",
|
||||
"flake8-bugbear==24.12.12",
|
||||
"flake8-encodings==0.5.1",
|
||||
"flake8-comprehensions==3.16.0",
|
||||
@@ -133,22 +133,22 @@ dev = [
|
||||
"flake8-no-implicit-concat==0.3.5",
|
||||
"flake8-print==5.0.0",
|
||||
"flake8-todos==0.3.1",
|
||||
"flake8-simplify==0.21.0",
|
||||
"flake8-simplify==0.22.0",
|
||||
"flake8-use-pathlib==0.3.0",
|
||||
"flake8-copyright==0.2.4",
|
||||
"ruff==0.9.2",
|
||||
"ruff==0.12.0",
|
||||
"black==25.1.0",
|
||||
"isort==6.0.0",
|
||||
"mypy==1.14.1",
|
||||
"mypy==1.16.0",
|
||||
"mypy-protobuf==3.6.0",
|
||||
"PyGithub==2.5.0",
|
||||
"PyGithub==2.6.0",
|
||||
# type stubs for mypy
|
||||
"types-backports==0.1.3",
|
||||
"types-colorama==0.4.15.11",
|
||||
"types-PyYAML==6.0.8",
|
||||
"types-psutil==6.1.0.20241102",
|
||||
"types-psutil==7.0.0.20250218",
|
||||
"types_requests==2.32.0.20240712",
|
||||
"types-protobuf==5.29.1.20241207",
|
||||
"types-protobuf==6.30.2.20250516",
|
||||
"deptry==0.23.0"
|
||||
]
|
||||
build = [
|
||||
@@ -156,13 +156,13 @@ build = [
|
||||
# we want all developer environments to be consistent.
|
||||
# These dependencies are not used in production environments
|
||||
# and should not conflict with other libraries/tooling.
|
||||
"pyinstaller==6.11.1",
|
||||
"setuptools==75.8.0",
|
||||
"pyinstaller==6.14.1",
|
||||
"setuptools==80.9.0",
|
||||
"build==1.2.2"
|
||||
]
|
||||
scripts = [
|
||||
"jschema_to_python==1.2.3",
|
||||
"psutil==6.1.0",
|
||||
"psutil==7.0.0",
|
||||
"stix2==3.0.1",
|
||||
"sarif_om==1.0.4",
|
||||
"requests==2.32.3",
|
||||
|
||||
@@ -12,7 +12,7 @@ cxxfilt==0.3.0
|
||||
dncil==1.0.2
|
||||
dnfile==0.15.0
|
||||
funcy==2.0
|
||||
humanize==4.10.0
|
||||
humanize==4.12.0
|
||||
ida-netnode==3.0
|
||||
ida-settings==2.1.0
|
||||
intervaltree==3.1.0
|
||||
@@ -21,25 +21,25 @@ mdurl==0.1.2
|
||||
msgpack==1.0.8
|
||||
networkx==3.4.2
|
||||
pefile==2024.8.26
|
||||
pip==25.0
|
||||
protobuf==5.29.3
|
||||
pip==25.1.1
|
||||
protobuf==6.31.1
|
||||
pyasn1==0.5.1
|
||||
pyasn1-modules==0.3.0
|
||||
pycparser==2.22
|
||||
pydantic==2.10.1
|
||||
pydantic==2.11.4
|
||||
# pydantic pins pydantic-core,
|
||||
# but dependabot updates these separately (which is broken) and is annoying,
|
||||
# so we rely on pydantic to pull in the right version of pydantic-core.
|
||||
# pydantic-core==2.23.4
|
||||
xmltodict==0.14.2
|
||||
pyelftools==0.31
|
||||
pyelftools==0.32
|
||||
pygments==2.19.1
|
||||
python-flirt==0.9.2
|
||||
pyyaml==6.0.2
|
||||
rich==13.9.2
|
||||
rich==14.0.0
|
||||
ruamel-yaml==0.18.6
|
||||
ruamel-yaml-clib==0.2.8
|
||||
setuptools==75.8.0
|
||||
setuptools==80.9.0
|
||||
six==1.17.0
|
||||
sortedcontainers==2.4.0
|
||||
viv-utils==0.8.0
|
||||
|
||||
2
rules
2
rules
Submodule rules updated: 79afc557f1...2f09b4d471
@@ -175,8 +175,6 @@ def convert_rule(rule, rulename, cround, depth):
|
||||
depth += 1
|
||||
logger.info("recursion depth: %d", depth)
|
||||
|
||||
global var_names
|
||||
|
||||
def do_statement(s_type, kid):
|
||||
yara_strings = ""
|
||||
yara_condition = ""
|
||||
|
||||
@@ -49,7 +49,7 @@ import capa.helpers
|
||||
import capa.features.insn
|
||||
import capa.capabilities.common
|
||||
from capa.rules import Rule, RuleSet
|
||||
from capa.features.common import OS_AUTO, String, Feature, Substring
|
||||
from capa.features.common import OS_AUTO, Regex, String, Feature, Substring
|
||||
from capa.render.result_document import RuleMetadata
|
||||
|
||||
logger = logging.getLogger("lint")
|
||||
@@ -406,6 +406,7 @@ class DoesntMatchExample(Lint):
|
||||
return True
|
||||
|
||||
if rule.name not in capabilities:
|
||||
logger.info('rule "%s" does not match for sample %s', rule.name, example_id)
|
||||
return True
|
||||
|
||||
|
||||
@@ -721,6 +722,76 @@ class FeatureStringTooShort(Lint):
|
||||
return False
|
||||
|
||||
|
||||
class FeatureRegexRegistryControlSetMatchIncomplete(Lint):
|
||||
name = "feature regex registry control set match incomplete"
|
||||
recommendation = (
|
||||
'use "(ControlSet\\d{3}|CurrentControlSet)" to match both indirect references '
|
||||
+ 'via "CurrentControlSet" and direct references via "ControlSetXXX"'
|
||||
)
|
||||
|
||||
def check_features(self, ctx: Context, features: list[Feature]):
|
||||
for feature in features:
|
||||
if not isinstance(feature, (Regex,)):
|
||||
continue
|
||||
|
||||
assert isinstance(feature.value, str)
|
||||
|
||||
pat = feature.value.lower()
|
||||
|
||||
if "system\\\\" in pat and "controlset" in pat or "currentcontrolset" in pat:
|
||||
if "system\\\\(controlset\\d{3}|currentcontrolset)" not in pat:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class FeatureRegexContainsUnescapedPeriod(Lint):
|
||||
name = "feature regex contains unescaped period"
|
||||
recommendation_template = 'escape the period in "{:s}" unless it should be treated as a regex dot operator'
|
||||
level = Lint.WARN
|
||||
|
||||
def check_features(self, ctx: Context, features: list[Feature]):
|
||||
for feature in features:
|
||||
if isinstance(feature, (Regex,)):
|
||||
assert isinstance(feature.value, str)
|
||||
|
||||
pat = feature.value.removeprefix("/")
|
||||
pat = pat.removesuffix("/i").removesuffix("/")
|
||||
|
||||
index = pat.find(".")
|
||||
if index == -1:
|
||||
return False
|
||||
|
||||
if index < len(pat) - 1:
|
||||
if pat[index + 1] in ("*", "+", "?", "{"):
|
||||
# like "/VB5!.*/"
|
||||
return False
|
||||
|
||||
if index == 0:
|
||||
# like "/.exe/" which should be "/\.exe/"
|
||||
self.recommendation = self.recommendation_template.format(feature.value)
|
||||
return True
|
||||
|
||||
if pat[index - 1] != "\\":
|
||||
# like "/test.exe/" which should be "/test\.exe/"
|
||||
self.recommendation = self.recommendation_template.format(feature.value)
|
||||
return True
|
||||
|
||||
if pat[index - 1] == "\\":
|
||||
for i, char in enumerate(pat[0:index][::-1]):
|
||||
if char == "\\":
|
||||
continue
|
||||
|
||||
if i % 2 == 0:
|
||||
# like "/\\\\.\\pipe\\VBoxTrayIPC/"
|
||||
self.recommendation = self.recommendation_template.format(feature.value)
|
||||
return True
|
||||
|
||||
break
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class FeatureNegativeNumber(Lint):
|
||||
name = "feature value is negative"
|
||||
recommendation = "specify the number's two's complement representation"
|
||||
@@ -931,7 +1002,13 @@ def lint_meta(ctx: Context, rule: Rule):
|
||||
return run_lints(META_LINTS, ctx, rule)
|
||||
|
||||
|
||||
FEATURE_LINTS = (FeatureStringTooShort(), FeatureNegativeNumber(), FeatureNtdllNtoskrnlApi())
|
||||
FEATURE_LINTS = (
|
||||
FeatureStringTooShort(),
|
||||
FeatureNegativeNumber(),
|
||||
FeatureNtdllNtoskrnlApi(),
|
||||
FeatureRegexContainsUnescapedPeriod(),
|
||||
FeatureRegexRegistryControlSetMatchIncomplete(),
|
||||
)
|
||||
|
||||
|
||||
def lint_features(ctx: Context, rule: Rule):
|
||||
|
||||
Submodule tests/data updated: 6cf615dd01...836bd7acc0
@@ -70,4 +70,4 @@ def test_standalone_binja_backend():
|
||||
@pytest.mark.skipif(binja_present is False, reason="Skip binja tests if the binaryninja Python API is not installed")
|
||||
def test_binja_version():
|
||||
version = binaryninja.core_version_info()
|
||||
assert version.major == 4 and version.minor == 2
|
||||
assert version.major == 5 and version.minor == 0
|
||||
|
||||
1357
web/explorer/package-lock.json
generated
1357
web/explorer/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -26,15 +26,15 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.8.0",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.23.0",
|
||||
"jsdom": "^24.1.0",
|
||||
"prettier": "^3.2.5",
|
||||
"vite": "^5.4.14",
|
||||
"vite-plugin-singlefile": "^2.0.2",
|
||||
"vitest": "^1.6.0"
|
||||
"vite": "^6.3.4",
|
||||
"vite-plugin-singlefile": "^2.2.0",
|
||||
"vitest": "^3.0.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,27 +210,25 @@
|
||||
<div class="row flex-lg-row-reverse align-items-center g-5">
|
||||
<h1>What's New</h1>
|
||||
|
||||
<h2 class="mt-3">Rule Updates</h2>
|
||||
|
||||
<ul class="mt-2 ps-5">
|
||||
<!-- TODO(williballenthin): add date -->
|
||||
<li>
|
||||
added:
|
||||
<a href="./rules/use bigint function/">
|
||||
use bigint function
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
added:
|
||||
<a href="./rules/encrypt data using RSA via embedded library/">
|
||||
encrypt data using RSA via embedded library
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2 class="mt-3">Tool Updates</h2>
|
||||
|
||||
<h3 class="mt-2">v9.2.1 (<em>2025-06-06</em>)</h3>
|
||||
<p class="mt-0">
|
||||
This point release fixes bugs including removing an unnecessary PyInstaller warning message and enabling the standalone binary to execute on systems running older versions of glibc.
|
||||
</p>
|
||||
|
||||
<h3 class="mt-2">v9.2.0 (<em>2025-06-03</em>)</h3>
|
||||
<p class="mt-0">
|
||||
This release improves a few aspects of dynamic analysis, including relaxing our validation on fields across many CAPE versions and processing additional VMRay submission file types, for example.
|
||||
It also includes an updated rule pack containing new rules and rule fixes.
|
||||
</p>
|
||||
|
||||
<h3 class="mt-2">v9.1.0 (<em>2025-03-02</em>)</h3>
|
||||
<p class="mt-0">
|
||||
This release improves a few aspects of dynamic analysis, relaxing our validation on fields across many CAPE versions, for example.
|
||||
It also includes an updated rule pack in which many dynamic rules make better use of the "span of calls" scope.
|
||||
</p>
|
||||
|
||||
<h3 class="mt-2">v9.0.0 (<em>2025-02-05</em>)</h3>
|
||||
<p class="mt-0">
|
||||
This release introduces a new scope for dynamic analysis, "span of calls",
|
||||
|
||||
Reference in New Issue
Block a user