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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .clusterfuzzlite/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM gcr.io/oss-fuzz-base/base-builder-python

COPY ./.clusterfuzzlite/requirements-atheris.txt $SRC/requirements-atheris.txt
RUN python3 -m pip install --no-cache-dir --no-binary=:all: --no-deps \
-r $SRC/requirements-atheris.txt

COPY . $SRC/codex-plugin-scanner
WORKDIR $SRC/codex-plugin-scanner
COPY ./.clusterfuzzlite/build.sh $SRC/build.sh
8 changes: 8 additions & 0 deletions .clusterfuzzlite/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash -eu

cd "$SRC/codex-plugin-scanner"
export PYTHONPATH="$PWD/src${PYTHONPATH:+:$PYTHONPATH}"

for fuzzer in fuzzers/*_fuzzer.py; do
compile_python_fuzzer "$fuzzer"
done
3 changes: 3 additions & 0 deletions .clusterfuzzlite/project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
homepage: "https://github.com/hashgraph-online/codex-plugin-scanner"
language: python
main_repo: "https://github.com/hashgraph-online/codex-plugin-scanner"
1 change: 1 addition & 0 deletions .clusterfuzzlite/requirements-atheris.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
atheris==3.0.0 --hash=sha256:1f0929c7bc3040f3fe4102e557718734190cf2d7718bbb8e3ce6d3eb56ef5bb3
20 changes: 12 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ jobs:
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: ${{ matrix.python-version }}
cache: pip
- run: pip install -e ".[dev]"
- run: ruff check src/
- run: ruff format --check src/
- run: pytest --tb=short
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e
with:
enable-cache: true
- run: uv sync --frozen --extra dev --python ${{ matrix.python-version }}
- run: uv run --no-sync ruff check src/
- run: uv run --no-sync ruff format --check src/
- run: uv run --no-sync pytest --tb=short

cross-platform:
runs-on: ${{ matrix.os }}
Expand All @@ -36,6 +38,8 @@ jobs:
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: ${{ matrix.python-version }}
cache: pip
- run: pip install -e ".[dev]"
- run: pytest tests/test_cli.py tests/test_verification.py tests/test_action_runner.py --tb=short
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e
with:
enable-cache: true
- run: uv sync --frozen --extra dev --python ${{ matrix.python-version }}
- run: uv run --no-sync pytest tests/test_cli.py tests/test_verification.py tests/test_action_runner.py --tb=short
35 changes: 35 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: CodeQL

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: "0 4 * * 1"

permissions:
contents: read

jobs:
analyze:
name: Analyze (${{ matrix.language }})
if: ${{ vars.CODEQL_ADVANCED_ENABLED == 'true' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [actions, python]
permissions:
actions: read
contents: read
security-events: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d
with:
languages: ${{ matrix.language }}
build-mode: none
- uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d
with:
category: /language:${{ matrix.language }}
53 changes: 53 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Fuzzing

on:
pull_request:
branches: [main]
push:
branches: [main]
schedule:
- cron: "0 6 * * 1"

permissions:
contents: read

jobs:
code-change:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: google/clusterfuzzlite/actions/build_fuzzers@52ecc61cb587ee99c26825a112a21abf19c7448c
with:
language: python
sanitizer: address
- uses: google/clusterfuzzlite/actions/run_fuzzers@52ecc61cb587ee99c26825a112a21abf19c7448c
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
mode: code-change
sanitizer: address
fuzz-seconds: 600
output-sarif: true

batch:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: google/clusterfuzzlite/actions/build_fuzzers@52ecc61cb587ee99c26825a112a21abf19c7448c
with:
language: python
sanitizer: address
- uses: google/clusterfuzzlite/actions/run_fuzzers@52ecc61cb587ee99c26825a112a21abf19c7448c
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
mode: batch
sanitizer: address
fuzz-seconds: 1800
output-sarif: true
15 changes: 7 additions & 8 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,11 @@ jobs:
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: "3.12"
cache: "pip"
cache-dependency-path: pyproject.toml
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e
with:
enable-cache: true
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build twine
run: uv sync --frozen --extra dev --group publish
- name: Compute publish version
id: version
env:
Expand All @@ -60,9 +59,9 @@ jobs:
sed -i "1,/^version = /{s/^version = .*/version = \"$VERSION\"/}" pyproject.toml
sed -i "1,/^__version__ = /{s/^__version__ = .*/__version__ = \"$VERSION\"/}" src/codex_plugin_scanner/version.py
- name: Build package
run: python -m build
run: uv run --no-sync python -m build
- name: Verify distributions
run: twine check dist/*
run: uv run --no-sync twine check dist/*
- name: Upload artifacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
Expand Down Expand Up @@ -149,7 +148,7 @@ jobs:

### Installation
\`\`\`bash
pip install codex-plugin-scanner==${VERSION}
uv tool install codex-plugin-scanner==${VERSION}
\`\`\`

**Full Changelog**: https://github.com/hashgraph-online/codex-plugin-scanner/compare/${LAST_TAG}...v${VERSION}
Expand Down
19 changes: 19 additions & 0 deletions fuzzers/manifest_fuzzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import sys

import atheris

with atheris.instrument_imports():
from codex_plugin_scanner.checks.manifest import load_manifest_text


def test_one_input(data: bytes) -> None:
load_manifest_text(data.decode("utf-8", errors="ignore"))


def main() -> None:
atheris.Setup(sys.argv, test_one_input)
atheris.Fuzz()


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ dev = [
"ruff>=0.4.0",
]

[dependency-groups]
publish = [
"twine>=6.1.0",
]

[project.scripts]
codex-plugin-scanner = "codex_plugin_scanner.cli:main"

Expand Down
12 changes: 10 additions & 2 deletions src/codex_plugin_scanner/checks/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,21 @@
KEBAB_RE = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")


def load_manifest_text(raw_text: str) -> dict | None:
try:
parsed = json.loads(raw_text)
except (json.JSONDecodeError, TypeError):
return None
return parsed if isinstance(parsed, dict) else None


def load_manifest(plugin_dir: Path) -> dict | None:
path = plugin_dir / ".codex-plugin" / "plugin.json"
if not path.exists():
return None
try:
return json.loads(path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return load_manifest_text(path.read_text(encoding="utf-8"))
except (OSError, ValueError):
return None


Expand Down
19 changes: 19 additions & 0 deletions tests/test_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,23 @@
check_semver,
check_valid_json,
load_manifest,
load_manifest_text,
run_manifest_checks,
)

FIXTURES = Path(__file__).parent / "fixtures"


class TestLoadManifest:
def test_load_manifest_text_returns_dict_for_valid_json_text(self):
result = load_manifest_text('{"name":"example-good-plugin","version":"1.0.0","description":"ok"}')
assert isinstance(result, dict)
assert result["name"] == "example-good-plugin"

def test_load_manifest_text_returns_none_for_invalid_json_text(self):
result = load_manifest_text("{invalid")
assert result is None

def test_returns_dict_for_valid_json(self):
result = load_manifest(FIXTURES / "good-plugin")
assert isinstance(result, dict)
Expand All @@ -37,6 +47,15 @@ def test_returns_none_for_nonexistent_path(self):
result = load_manifest(Path(tmpdir) / "nonexistent")
assert result is None

def test_returns_none_for_non_utf8_manifest(self, tmp_path: Path):
manifest_dir = tmp_path / ".codex-plugin"
manifest_dir.mkdir(parents=True)
(manifest_dir / "plugin.json").write_bytes(b"\xff\xfe\x00\x00")

result = load_manifest(tmp_path)

assert result is None


class TestCheckPluginJsonExists:
def test_passes_when_exists(self):
Expand Down
Loading
Loading