diff --git a/.github/workflows/frapast-scan.yml b/.github/workflows/frapast-scan.yml index 7165890..02e3b84 100644 --- a/.github/workflows/frapast-scan.yml +++ b/.github/workflows/frapast-scan.yml @@ -6,6 +6,10 @@ on: pull_request: branches: [ main, develop ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: action-test: name: Test Composite Action (action.yml) diff --git a/.github/workflows/scanner-ci.yml b/.github/workflows/scanner-ci.yml index cf0112a..19e75d4 100644 --- a/.github/workflows/scanner-ci.yml +++ b/.github/workflows/scanner-ci.yml @@ -10,6 +10,10 @@ permissions: contents: read security-events: write # required for SARIF upload to Code Scanning +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: name: Tests (Python ${{ matrix.python-version }}) @@ -114,7 +118,7 @@ jobs: " - name: Upload SARIF to GitHub Code Scanning - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 if: always() with: sarif_file: frapast-self.sarif diff --git a/README.md b/README.md index 9c80a3c..03dd05a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) [![Tests](https://img.shields.io/badge/tests-306%20passed-green.svg)](#test-suite-and-quality-assurance) -[![Rules](https://img.shields.io/badge/detectors-33%20active-blue.svg)](#rule-taxonomy) +[![Rules](https://img.shields.io/badge/detectors-28%20active-blue.svg)](#rule-taxonomy) [![Benchmark Speed](https://img.shields.io/badge/speed-16%2C700%2B%20files%2Fsec-green.svg)](#industry-benchmarks-and-performance) [![Privacy](https://img.shields.io/badge/privacy-100%25%20Local%20and%20Airgapped-blue.svg)](#data-privacy-and-air-gapped-execution) @@ -90,7 +90,7 @@ frapAST includes live integration testing against local and staging Frappe bench ## Architectural Comparison -| Capability | Generic SAST (Bandit / SonarQube) | Cloud Static Scanners | frapAST | +| Capability | Generic SAST Tools | Cloud Static Scanners | frapAST | |---|:---:|:---:|:---:| | Frappe Framework Modeling | None (generic Python only) | Partial regex matching | Full (DocType JSONs, `hooks.py`, ORM, DocEvents, Reports) | | Active Proof Verification | None (static alerts only) | None (static alerts only) | Two-Tier Active Proof (synthesizes live HTTP reproducers) | diff --git a/action.yml b/action.yml index c5413b8..5f57b31 100644 --- a/action.yml +++ b/action.yml @@ -119,7 +119,7 @@ runs: - name: Upload SARIF to GitHub Code Scanning if: ${{ inputs.upload-sarif == 'true' && always() }} - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: ${{ inputs.sarif-file }} category: frapast diff --git a/pyproject.toml b/pyproject.toml index 9712988..4263424 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", ] requires-python = ">=3.10" diff --git a/scanner/cli.py b/scanner/cli.py index 3ca4f50..3c55248 100644 --- a/scanner/cli.py +++ b/scanner/cli.py @@ -503,6 +503,10 @@ def _build_parser() -> argparse.ArgumentParser: frapast scan /path/to/erpnext frapast scan /path/to/frappe-app --severity --format json frapast scan --config scan_config.yaml + +Exit Codes: + 0 Success (clean scan, findings below failure threshold, or command succeeded) + 1 Execution error, invalid configuration, or unhandled exception """, ) parser.add_argument("--version", action="version", version=_get_version_string()) diff --git a/scanner/schema/engine.py b/scanner/schema/engine.py index 2c14f0b..771686a 100644 --- a/scanner/schema/engine.py +++ b/scanner/schema/engine.py @@ -27,12 +27,15 @@ def discover_doctype_json(app_roots: str | Path | list[str | Path] | tuple[str | d for d in dirs if d not in SKIP_DIRS and not (d.startswith(".") and d != ".") ] - parent_name = Path(dirpath).name + parent_name = os.path.basename(dirpath) + dir_path_obj: Path | None = None for fname in sorted(filenames): if fname.endswith(".json"): stem = fname[:-5] if parent_name == stem: - files.append(SourceFile(path=Path(dirpath) / fname, root=root)) + if dir_path_obj is None: + dir_path_obj = Path(dirpath) + files.append(SourceFile(path=dir_path_obj / fname, root=root)) return files diff --git a/scanner/suppression.py b/scanner/suppression.py index dc5315b..69c3a1e 100644 --- a/scanner/suppression.py +++ b/scanner/suppression.py @@ -71,7 +71,7 @@ def is_suppressed( """Return True if the candidate has an inline ``# frapast: ignore`` suppression. Checks the same line as the finding and the immediately preceding line, which is - the standard convention from Bandit/ESLint/ruff. + the standard convention from popular linters and static analyzers. Parameters ---------- diff --git a/site/index.html b/site/index.html index ed535a4..e448edb 100644 --- a/site/index.html +++ b/site/index.html @@ -123,7 +123,7 @@

Most scanners guess.
frapAST proves it.

-

Why not just use Bandit or SonarQube?

+

Why not generic SAST scanners?

Generic Python SAST tools analyze Frappe apps as generic scripts. Frappe applications are metadata-driven: DocType JSON schemas, hooks.py event registrations, and whitelist decorators cannot be understood by plain AST linters without producing heavy false-positive noise.

@@ -373,7 +373,7 @@

Frequently Asked Questions