├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── docs.yml │ ├── pypi-publish.yml │ ├── release.yml │ └── version-bump.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .prettierrc ├── .python-version ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── IMPLEMENTATION.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── TODO.md ├── assets └── logo.svg ├── docs ├── README.md ├── advanced │ ├── automating-validation.md │ ├── ci-cd-integration.md │ ├── contributing.md │ ├── enterprise-deployment.md │ ├── opa-integration.md │ └── performance-optimization.md ├── api │ ├── core.md │ ├── index.md │ ├── loader.md │ ├── reference.md │ ├── rules.md │ ├── types.md │ └── validator.md ├── assets │ └── images │ │ ├── demo.yml │ │ ├── logo.svg │ │ ├── plan-lint-001.gif │ │ └── plan-lint-002.gif ├── contributing.md ├── documentation │ ├── api-reference.md │ ├── custom-rule-development.md │ ├── index.md │ ├── mcp-integration.md │ ├── plan-structure.md │ ├── policy-formats.md │ ├── risk-scoring.md │ └── rule-types.md ├── examples │ ├── custom-rules.md │ ├── finance-agent-system.md │ ├── index.md │ └── sql-injection.md ├── getting-started.md ├── index.md ├── policy-authoring.md └── stylesheets │ └── logo.svg ├── examples ├── README.md ├── __init__.py ├── benchmark_linter.py ├── finance_agent_system │ ├── README.md │ ├── __init__.py │ ├── agent_system.py │ ├── finance_policy.rego │ ├── finance_policy.yaml │ ├── main.py │ ├── requirements.txt │ ├── test_opa.py │ └── validator.py ├── interactive_demo.py ├── opa_validation_demo.py ├── realistic_demo.py └── validator_example.py ├── mkdocs.yml ├── pyproject.toml ├── requirements-dev.txt ├── requirements.txt ├── src └── plan_lint │ ├── __init__.py │ ├── __main__.py │ ├── cli.py │ ├── core.py │ ├── examples │ ├── policy.yaml │ └── price_drop.json │ ├── loader.py │ ├── opa.py │ ├── reporters │ ├── __init__.py │ ├── cli.py │ └── json.py │ ├── rules │ ├── __init__.py │ ├── deny_sql_write.py │ └── no_raw_secret.py │ ├── schemas │ └── plan.schema.json │ └── types.py └── tests ├── conftest.py ├── test_cli.py ├── test_core.py └── test_opa.py /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.{yml,yaml,json,toml}] 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | 17 | [Makefile] 18 | indent_style = tab -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '[BUG] ' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## Bug Description 10 | A clear and concise description of the bug. 11 | 12 | ## Steps To Reproduce 13 | 1. ... 14 | 2. ... 15 | 3. ... 16 | 17 | ## Expected Behavior 18 | A clear and concise description of what you expected to happen. 19 | 20 | ## Actual Behavior 21 | What actually happened instead. 22 | 23 | ## Environment 24 | - OS: [e.g. Ubuntu 22.04, macOS 13.0] 25 | - Python version: [e.g. 3.11.5] 26 | - plan-lint version: [e.g. 0.0.1] 27 | 28 | ## Additional Context 29 | Add any other context about the problem here, such as example files or error logs. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '[FEATURE] ' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Problem Statement 10 | A clear and concise description of what problem this feature would solve. Ex. I'm always frustrated when [...] 11 | 12 | ## Proposed Solution 13 | A clear and concise description of what you want to happen. 14 | 15 | ## Alternative Solutions 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | ## Example Use Case 19 | Describe how users would use this feature and what value it provides. 20 | 21 | ## Additional Context 22 | Add any other context, mockups, or examples about the feature request here. -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | ## Related Issues 5 | 6 | 7 | ## Type of Change 8 | 9 | - [ ] Bug fix (non-breaking change that fixes an issue) 10 | - [ ] New feature (non-breaking change that adds functionality) 11 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 12 | - [ ] Documentation update 13 | - [ ] Testing improvements 14 | - [ ] Other (please describe): 15 | 16 | ## Testing 17 | 18 | - [ ] Added tests that cover the changes 19 | - [ ] All tests pass locally 20 | 21 | ## Checklist 22 | 23 | - [ ] My code follows the code style of this project 24 | - [ ] I have performed a self-review of my own code 25 | - [ ] I have commented my code, particularly in hard-to-understand areas 26 | - [ ] I have made corresponding changes to the documentation 27 | - [ ] My changes generate no new warnings 28 | - [ ] I have added tests that prove my fix is effective or that my feature works 29 | 30 | ## Additional Notes 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main, staging ] 6 | pull_request: 7 | branches: [ main, staging ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python-version: ['3.11', '3.12'] 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | 23 | - name: Setup uv 24 | uses: astral-sh/setup-uv@v5 25 | with: 26 | enable-cache: true 27 | 28 | - name: Install dependencies 29 | run: | 30 | uv pip install --system -e ".[dev]" 31 | uv pip install --system types-jsonschema types-PyYAML --break-system-packages 32 | 33 | - name: Lint with ruff 34 | run: | 35 | ruff check . 36 | 37 | - name: Type check with mypy 38 | run: | 39 | mypy src 40 | 41 | - name: Test with pytest 42 | run: | 43 | pytest tests/ --cov=src/plan_lint --cov-report=xml 44 | 45 | - name: Upload coverage to Codecov 46 | uses: codecov/codecov-action@v5 47 | with: 48 | files: ./coverage.xml 49 | fail_ci_if_error: false 50 | 51 | lint-test-plans: 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v4 55 | - name: Set up Python 56 | uses: actions/setup-python@v5 57 | with: 58 | python-version: '3.11' 59 | 60 | - name: Setup uv 61 | uses: astral-sh/setup-uv@v5 62 | with: 63 | enable-cache: true 64 | 65 | - name: Install dependencies 66 | run: | 67 | uv pip install --system -e . 68 | 69 | - name: Test example plans with linter 70 | run: | 71 | python -m plan_lint src/plan_lint/examples/price_drop.json --policy src/plan_lint/examples/policy.yaml 72 | continue-on-error: true 73 | 74 | - name: Verify linter behavior 75 | run: | 76 | # Run the linter and capture the exit code 77 | python -m plan_lint src/plan_lint/examples/price_drop.json --policy src/plan_lint/examples/policy.yaml --format json > linter_output.json || echo "Linter found issues as expected" 78 | 79 | # Check that the output contains the expected errors 80 | grep -q "TOOL_DENY" linter_output.json && grep -q "RAW_SECRET" linter_output.json && echo "✅ Linter correctly identified policy violations" 81 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'docs/**' 9 | - 'mkdocs.yml' 10 | - '.github/workflows/docs.yml' 11 | # Allow manual triggering 12 | workflow_dispatch: 13 | inputs: 14 | version: 15 | description: 'Version to deploy (e.g., latest, 0.1.0)' 16 | required: false 17 | default: 'latest' 18 | alias: 19 | description: 'Version alias (e.g., latest, stable)' 20 | required: false 21 | default: 'latest' 22 | 23 | jobs: 24 | deploy-docs: 25 | name: Deploy Documentation 26 | runs-on: ubuntu-latest 27 | permissions: 28 | contents: write 29 | steps: 30 | - uses: actions/checkout@v4 31 | with: 32 | fetch-depth: 0 33 | 34 | - name: Set up Python 35 | uses: actions/setup-python@v5 36 | with: 37 | python-version: '3.11' 38 | cache: 'pip' 39 | 40 | - name: Install dependencies 41 | run: | 42 | python -m pip install --upgrade pip 43 | pip install -e ".[docs]" 44 | pip install mike 45 | 46 | - name: Configure Git 47 | run: | 48 | git config --local user.name "GitHub Actions" 49 | git config --local user.email "actions@github.com" 50 | 51 | - name: Get version from package 52 | id: get_version 53 | run: | 54 | VERSION=$(python -c "from importlib.metadata import version; print(version('plan-lint'))") 55 | echo "version=$VERSION" >> $GITHUB_OUTPUT 56 | shell: bash 57 | 58 | - name: Deploy docs as latest 59 | if: github.event_name == 'push' || github.event.inputs.version == 'latest' 60 | run: | 61 | mike deploy --push --update-aliases latest 62 | mike set-default --push latest 63 | 64 | - name: Deploy versioned docs 65 | if: github.event_name == 'workflow_dispatch' && github.event.inputs.version != 'latest' 66 | run: | 67 | VERSION=${{ github.event.inputs.version }} 68 | ALIAS=${{ github.event.inputs.alias || 'stable' }} 69 | 70 | mike deploy --push $VERSION $ALIAS 71 | 72 | # Update default if alias is stable 73 | if [ "$ALIAS" == "stable" ]; then 74 | mike set-default --push $VERSION 75 | fi -------------------------------------------------------------------------------- /.github/workflows/pypi-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' # Publish when a new version tag is pushed 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | environment: pypi-publish 12 | permissions: 13 | id-token: write # OIDC authentication with PyPI 14 | contents: read # To check out the repository 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: '3.11' 23 | 24 | - name: Setup uv 25 | uses: astral-sh/setup-uv@v5 26 | with: 27 | enable-cache: true 28 | 29 | - name: Install dependencies 30 | run: | 31 | uv pip install --system --break-system-packages build 32 | 33 | - name: Build package 34 | run: | 35 | uv build 36 | 37 | - name: Publish to PyPI 38 | uses: pypa/gh-action-pypi-publish@release/v1 39 | with: 40 | verbose: true 41 | print-hash: true -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | environment: pypi-publish 12 | permissions: 13 | contents: read 14 | id-token: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Set up Python 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: '3.11' 22 | 23 | - name: Setup uv 24 | uses: astral-sh/setup-uv@v5 25 | with: 26 | enable-cache: true 27 | 28 | - name: Install dependencies 29 | run: | 30 | uv pip install --system build twine --break-system-packages 31 | 32 | - name: Build package 33 | run: | 34 | python -m build 35 | 36 | - name: List dist contents 37 | run: | 38 | ls -la dist/ 39 | 40 | - name: Publish to PyPI 41 | uses: pypa/gh-action-pypi-publish@release/v1 42 | if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || github.event.inputs.publish == 'true' 43 | -------------------------------------------------------------------------------- /.github/workflows/version-bump.yml: -------------------------------------------------------------------------------- 1 | name: Version Bump 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - 'pyproject.toml' # Avoid recursion when the version bump itself is pushed 9 | 10 | jobs: 11 | bump-version: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Set up Python 22 | uses: actions/setup-python@v5 23 | with: 24 | python-version: '3.11' 25 | 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install toml 30 | 31 | - name: Bump version 32 | id: bump_version 33 | run: | 34 | # Get the current version from pyproject.toml 35 | CURRENT_VERSION=$(python -c "import toml; print(toml.load('pyproject.toml')['project']['version'])") 36 | echo "Current version: $CURRENT_VERSION" 37 | 38 | # Split the version into parts 39 | IFS='.' read -r -a VERSION_PARTS <<< "$CURRENT_VERSION" 40 | MAJOR="${VERSION_PARTS[0]}" 41 | MINOR="${VERSION_PARTS[1]}" 42 | PATCH="${VERSION_PARTS[2]}" 43 | 44 | # Increment patch version 45 | NEW_PATCH=$((PATCH + 1)) 46 | NEW_VERSION="$MAJOR.$MINOR.$NEW_PATCH" 47 | echo "New version: $NEW_VERSION" 48 | 49 | # Update pyproject.toml with the new version 50 | python -c " 51 | import toml 52 | data = toml.load('pyproject.toml') 53 | data['project']['version'] = '$NEW_VERSION' 54 | with open('pyproject.toml', 'w') as f: 55 | toml.dump(data, f) 56 | " 57 | 58 | # Export the new version for later steps 59 | echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT 60 | 61 | - name: Update files with new version 62 | run: | 63 | NEW_VERSION="${{ steps.bump_version.outputs.new_version }}" 64 | 65 | # Update version in __init__.py if it exists 66 | if [ -f "src/plan_lint/__init__.py" ]; then 67 | sed -i "s/__version__ = \".*\"/__version__ = \"$NEW_VERSION\"/" src/plan_lint/__init__.py 68 | fi 69 | 70 | # Update CHANGELOG.md with new version entry if it exists 71 | if [ -f "CHANGELOG.md" ]; then 72 | DATE=$(date +%Y-%m-%d) 73 | sed -i "1s/^/## $NEW_VERSION ($DATE)\n\n- Auto-version bump from GitHub Actions\n\n/" CHANGELOG.md 74 | fi 75 | 76 | - name: Commit and push changes 77 | run: | 78 | NEW_VERSION="${{ steps.bump_version.outputs.new_version }}" 79 | git config --local user.email "action@github.com" 80 | git config --local user.name "GitHub Action" 81 | git add pyproject.toml src/plan_lint/__init__.py CHANGELOG.md 82 | git commit -m "Bump version to $NEW_VERSION" 83 | git push origin main 84 | 85 | - name: Create tag 86 | run: | 87 | NEW_VERSION="${{ steps.bump_version.outputs.new_version }}" 88 | git tag "v$NEW_VERSION" 89 | git push origin "v$NEW_VERSION" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # Virtual environments 10 | .venv 11 | 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__/ 14 | *.py[cod] 15 | *$py.class 16 | 17 | # C extensions 18 | *.so 19 | 20 | # Distribution / packaging 21 | .Python 22 | build/ 23 | develop-eggs/ 24 | dist/ 25 | downloads/ 26 | eggs/ 27 | .eggs/ 28 | lib/ 29 | lib64/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | wheels/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | MANIFEST 38 | 39 | # PyInstaller 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .nox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *.cover 57 | .hypothesis/ 58 | .pytest_cache/ 59 | 60 | # Environments 61 | .env 62 | .venv 63 | env/ 64 | venv/ 65 | ENV/ 66 | env.bak/ 67 | venv.bak/ 68 | 69 | # IDE files 70 | .idea/ 71 | .vscode/ 72 | *.swp 73 | *.swo 74 | 75 | # OS generated files 76 | .DS_Store 77 | .DS_Store? 78 | ._* 79 | .Spotlight-V100 80 | .Trashes 81 | ehthumbs.db 82 | Thumbs.db 83 | .cursor 84 | 85 | # MkDocs documentation 86 | site/ 87 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.5.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | - id: check-added-large-files 9 | - id: check-json 10 | - id: debug-statements 11 | 12 | - repo: https://github.com/astral-sh/ruff-pre-commit 13 | rev: v0.1.11 14 | hooks: 15 | - id: ruff 16 | args: [--fix] 17 | - id: ruff-format 18 | 19 | - repo: https://github.com/pre-commit/mirrors-mypy 20 | rev: v1.8.0 21 | hooks: 22 | - id: mypy 23 | additional_dependencies: [types-PyYAML] -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "overrides": [ 4 | { 5 | "files": "*.yml", 6 | "options": { 7 | "tabWidth": 2 8 | } 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.11 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.4 (2025-04-29) 2 | 3 | - Auto-version bump from GitHub Actions 4 | 5 | # Changelog 6 | 7 | All notable changes to this project will be documented in this file. 8 | 9 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 10 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 11 | 12 | ## [Unreleased] 13 | 14 | ### Added 15 | - Initial project structure 16 | - Core validation functionality 17 | - Policy rule engine 18 | - CLI with rich text output 19 | - JSON output format 20 | - Basic rule plugins (SQL write detection, secret detection) 21 | - JSON schema validation 22 | - Bounds checking 23 | - Cycle detection 24 | 25 | ## [0.0.1] - 2023-04-27 26 | 27 | ### Added 28 | - Initial alpha release -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Project maintainers are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all community spaces, and also applies when 49 | an individual is officially representing the community in public spaces. 50 | 51 | ## Enforcement 52 | 53 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 54 | reported to the project maintainers. All complaints will be reviewed and 55 | investigated promptly and fairly. 56 | 57 | ## Attribution 58 | 59 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), 60 | version 2.0, available at 61 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 62 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Plan-Linter 2 | 3 | Thank you for considering contributing to Plan-Linter! This document provides guidelines and instructions for contributing. 4 | 5 | ## Code of Conduct 6 | 7 | By participating in this project, you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md). 8 | 9 | ## How Can I Contribute? 10 | 11 | ### Reporting Bugs 12 | 13 | Bug reports help us improve. When creating a bug report, please include: 14 | 15 | - A clear, descriptive title 16 | - Steps to reproduce the issue 17 | - Expected behavior vs. actual behavior 18 | - Environment details (OS, Python version, etc.) 19 | 20 | ### Suggesting Features 21 | 22 | Feature suggestions are welcome. Please provide: 23 | 24 | - A clear description of the feature 25 | - The problem it solves 26 | - Possible implementation approaches 27 | 28 | ### Pull Requests 29 | 30 | 1. Fork the repository 31 | 2. Create a new branch (`git checkout -b feature/amazing-feature`) 32 | 3. Make your changes 33 | 4. Run tests and linting (`pytest` and `pre-commit run --all-files`) 34 | 5. Commit your changes (`git commit -m 'Add amazing feature'`) 35 | 6. Push to the branch (`git push origin feature/amazing-feature`) 36 | 7. Open a Pull Request 37 | 38 | ## Development Setup 39 | 40 | 1. Clone the repository 41 | ```bash 42 | git clone https://github.com/cirbuk/plan-lint.git 43 | cd plan-lint 44 | ``` 45 | 46 | 2. Create a virtual environment and install dependencies 47 | ```bash 48 | python -m venv .venv 49 | source .venv/bin/activate # On Windows, use `.venv\Scripts\activate` 50 | pip install -e ".[dev]" 51 | ``` 52 | 53 | 3. Setup pre-commit hooks 54 | ```bash 55 | pip install pre-commit 56 | pre-commit install 57 | ``` 58 | 59 | ## Coding Standards 60 | 61 | - Follow PEP 8 style guidelines 62 | - Write docstrings in Google style format 63 | - Include type hints 64 | - Add tests for new functionality 65 | 66 | ## Testing 67 | 68 | Run tests with pytest: 69 | 70 | ```bash 71 | pytest 72 | ``` 73 | 74 | ## Documentation 75 | 76 | - Update documentation for new features or changes 77 | - Include docstrings for all public functions, classes, and methods 78 | 79 | Thank you for contributing to Plan-Linter! 80 | -------------------------------------------------------------------------------- /IMPLEMENTATION.md: -------------------------------------------------------------------------------- 1 | # Plan-Linter Implementation 2 | 3 | This document summarizes the implementation of the Plan-Linter tool based on the requirements in the README. 4 | 5 | ## Architecture 6 | 7 | The Plan-Linter has been implemented with the following components: 8 | 9 | 1. **Core Functionality** 10 | - `types.py`: Type definitions using Pydantic models 11 | - `core.py`: Rule enforcement and validation logic 12 | - `loader.py`: Loading and parsing schemas, plans, and policies 13 | 14 | 2. **Rules** 15 | - `rules/deny_sql_write.py`: Rule to prevent SQL write operations 16 | - `rules/no_raw_secret.py`: Rule to detect secrets in plans 17 | 18 | 3. **Reporters** 19 | - `reporters/cli.py`: CLI reporter using Rich for formatted output 20 | - `reporters/json.py`: JSON reporter for machine-readable output 21 | 22 | 4. **Command Line Interface** 23 | - `cli.py`: Command-line interface using Typer 24 | - `__main__.py`: Entry point for the package 25 | 26 | 5. **Examples** 27 | - `examples/price_drop.json`: Example plan with issues 28 | - `examples/policy.yaml`: Example policy file 29 | 30 | 6. **Tests** 31 | - `tests/test_core.py`: Tests for core validation logic 32 | - `tests/test_cli.py`: Tests for CLI functionality 33 | 34 | ## Features Implemented 35 | 36 | - ✅ Schema validation of plan JSON 37 | - ✅ Policy rules for tool controls 38 | - ✅ Bounds checking for numeric parameters 39 | - ✅ Secret detection in plan steps 40 | - ✅ Loop detection in step dependencies 41 | - ✅ Risk scoring based on detected issues 42 | - ✅ Plugin rule system 43 | - ✅ CLI and JSON output formats 44 | 45 | ## Usage 46 | 47 | The tool can be used as follows: 48 | 49 | ```bash 50 | # Basic usage 51 | plan-lint path/to/plan.json 52 | 53 | # With policy file 54 | plan-lint path/to/plan.json --policy path/to/policy.yaml 55 | 56 | # Output formats 57 | plan-lint path/to/plan.json --format json 58 | plan-lint path/to/plan.json --output results.json 59 | 60 | # Custom risk threshold 61 | plan-lint path/to/plan.json --fail-risk 0.7 62 | ``` 63 | 64 | ## Design Decisions 65 | 66 | 1. **Pydantic for Type Safety**: We used Pydantic models for robust validation and type safety. 67 | 68 | 2. **Plugin Architecture**: The rules are implemented as separate modules that are dynamically loaded, allowing for easy extension. 69 | 70 | 3. **Rich for CLI Output**: We used the Rich library for attractive and helpful console output. 71 | 72 | 4. **Risk Scoring**: A flexible risk scoring system allows different weights for different types of issues. 73 | 74 | 5. **Modular Reporters**: The reporting system is modular, making it easy to add new output formats. 75 | 76 | ## Next Steps 77 | 78 | 1. **More Rules**: Add more predefined rule modules for common security concerns. 79 | 80 | 2. **Continuous Integration**: Provide better examples of CI integration. 81 | 82 | 3. **Documentation**: Expand documentation with more usage examples and rule creation guides. 83 | 84 | 4. **Testing**: Expand test coverage, especially for edge cases. 85 | 86 | 5. **Rule Discovery**: Implement more robust rule discovery via entry points. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 mason 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: install install-dev install-docs test lint format clean docs serve-docs build-docs cleanup-docs 2 | 3 | # Default target 4 | all: install-dev lint test 5 | 6 | # Installation targets 7 | install: 8 | pip install -e . 9 | 10 | install-dev: 11 | pip install -e ".[dev]" 12 | 13 | install-docs: 14 | pip install -e ".[docs]" 15 | 16 | # Testing and quality targets 17 | test: 18 | pytest 19 | 20 | test-cov: 21 | pytest --cov=plan_lint --cov-report=term --cov-report=html 22 | 23 | lint: 24 | ruff check . 25 | black --check . 26 | isort --check-only --profile black . 27 | mypy . 28 | 29 | format: 30 | ruff check --fix . 31 | black . 32 | isort --profile black . 33 | 34 | # Documentation targets 35 | docs: build-docs 36 | 37 | serve-docs: 38 | mkdocs serve 39 | 40 | build-docs: 41 | mkdocs build 42 | 43 | cleanup-docs: 44 | @echo "Cleaning up duplicate documentation files..." 45 | @rm -f docs/documentation/api-reference.md 46 | @rm -f docs/documentation/examples.md 47 | @rm -f docs/documentation/getting-started.md 48 | @rm -f docs/documentation/policy-authoring-guide.md 49 | @rm -f docs/documentation/policy-authoring.md 50 | @rm -f docs/documentation/user-guide.md 51 | @echo "Documentation cleanup complete." 52 | 53 | # Cleanup 54 | clean: 55 | rm -rf build/ 56 | rm -rf dist/ 57 | rm -rf *.egg-info 58 | rm -rf .coverage 59 | rm -rf htmlcov/ 60 | rm -rf .pytest_cache/ 61 | rm -rf .ruff_cache/ 62 | rm -rf .mypy_cache/ 63 | rm -rf site/ 64 | find . -type d -name __pycache__ -exec rm -rf {} + 65 | find . -type f -name "*.pyc" -delete 66 | 67 | # Help 68 | help: 69 | @echo "Available targets:" 70 | @echo " install - Install package" 71 | @echo " install-dev - Install package with development dependencies" 72 | @echo " install-docs - Install package with documentation dependencies" 73 | @echo " test - Run tests" 74 | @echo " test-cov - Run tests with coverage report" 75 | @echo " lint - Run linting checks" 76 | @echo " format - Format code" 77 | @echo " docs - Build documentation" 78 | @echo " serve-docs - Serve documentation locally" 79 | @echo " build-docs - Build static documentation" 80 | @echo " cleanup-docs - Remove duplicate documentation files" 81 | @echo " clean - Clean build artifacts" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🛡️ plan-linter 2 | 3 | *"Secure your AI agents. Lint your LLM-generated plans before they break things."* 4 | 5 | ## 🚨 Why Plan Linting Matters 6 | 7 | Modern AI agents dynamically generate plans at runtime — deciding what actions to take, what tools to call, what goals to pursue. 8 | But LLMs hallucinate. Plans are often invalid, broken, unsafe, or even harmful 9 | 10 | - Unsafe: Plans might trigger dangerous tool use (e.g., "delete all data") 11 | - Invalid: Plans can miss mandatory parameters or violate tool schemas 12 | - Incoherent: Plans can contradict agent goals or deadlock execution 13 | - Unexecutable: Plans can reference missing tools or invalid operations 14 | 15 | plan-lint is a lightweight open source linter designed to validate, catch, and flag these dangerous plans before your agents act on them. 16 | 17 | Protect your users. Safeguard your agents. Build responsibly. 18 | 19 | 20 | `plan-lint` is an **open-source static analysis toolkit** for LLM agent **plans**. 21 | 22 | It parses the machine-readable plan emitted by a planner/brain, validates it against 23 | schemas, policy rules, and heuristics, and returns Pass / Fail with an 24 | annotated risk-score JSON. 25 | 26 | [](https://github.com/cirbuk/plan-lint/actions/workflows/ci.yml) 27 | [](https://github.com/cirbuk/plan-lint/actions/workflows/pypi-publish.yml) 28 | [](https://github.com/cirbuk/plan-lint/actions/workflows/docs.yml) 29 | [](LICENSE) 30 | [](https://pypi.org/project/plan-lint/) 31 | [](https://pypi.org/project/plan-lint/) 32 | 33 | ## 📦 Installation 34 | 35 | ### Using pip 36 | ```bash 37 | pip install plan-lint 38 | ``` 39 | 40 | ### From source 41 | ```bash 42 | git clone https://github.com/cirbuk/plan-lint.git 43 | cd plan-lint 44 | pip install -e . 45 | ``` 46 | 47 | ## 🚀 Quick Start 48 | 49 | The simplest way to use plan-linter is to run it on a plan JSON file: 50 | 51 | ```bash 52 | plan-lint path/to/plan.json 53 | ``` 54 | 55 | or use in your application 56 | ```python 57 | from plan_lint import lint_plan 58 | 59 | errors = lint_plan(plan_object) 60 | if errors: 61 | print(errors) 62 | ``` 63 | 64 | For a more advanced usage, you can provide a policy file: 65 | 66 | ```bash 67 | plan-lint path/to/plan.json --policy path/to/policy.yaml 68 | ``` 69 | 70 | ## 📝 Example Plan Format 71 | 72 | ```json 73 | { 74 | "goal": "Update product prices with a discount", 75 | "context": { 76 | "user_id": "admin-012", 77 | "department": "sales" 78 | }, 79 | "steps": [ 80 | { 81 | "id": "step-001", 82 | "tool": "sql.query_ro", 83 | "args": { 84 | "query": "SELECT product_id, current_price FROM products" 85 | }, 86 | "on_fail": "abort" 87 | }, 88 | { 89 | "id": "step-002", 90 | "tool": "priceAPI.bulkUpdate", 91 | "args": { 92 | "product_ids": ["${step-001.result.product_id}"], 93 | "discount_pct": -20 94 | } 95 | } 96 | ], 97 | "meta": { 98 | "planner": "gpt-4o", 99 | "created_at": "2025-05-15T14:30:00Z" 100 | } 101 | } 102 | ``` 103 | 104 | ## 📋 Example Policy Format 105 | 106 | ```yaml 107 | # policy.yaml 108 | allow_tools: 109 | - sql.query_ro 110 | - priceAPI.bulkUpdate 111 | bounds: 112 | priceAPI.bulkUpdate.discount_pct: [-40, 0] 113 | deny_tokens_regex: 114 | - "AWS_SECRET" 115 | - "API_KEY" 116 | max_steps: 50 117 | risk_weights: 118 | tool_write: 0.4 119 | raw_secret: 0.5 120 | loop: 0.3 121 | fail_risk_threshold: 0.8 122 | ``` 123 | 124 | For detailed information on creating policies, including advanced YAML policies and Rego policies with Open Policy Agent integration, see our [Policy Authoring Guide](docs/policy-authoring.md). 125 | 126 | ## 🔍 Command Line Options 127 | 128 | ``` 129 | Usage: plan-lint [OPTIONS] PLAN_FILE 130 | 131 | Options: 132 | --policy, -p TEXT Path to the policy YAML file 133 | --schema, -s TEXT Path to the JSON schema file 134 | --format, -f TEXT Output format (cli or json) [default: cli] 135 | --output, -o TEXT Path to write output [default: stdout] 136 | --fail-risk, -r FLOAT Risk score threshold for failure (0-1) [default: 0.8] 137 | --help Show this message and exit 138 | ``` 139 | 140 | ## 🧩 Adding Custom Rules 141 | 142 | You can create custom rules by adding Python files to the `plan_lint/rules` directory. Each rule file should contain a `check_plan` function that takes a `Plan` and a `Policy` object and returns a list of `PlanError` objects. 143 | 144 | Here's an example of a custom rule that checks for SQL write operations: 145 | 146 | ```python 147 | from typing import List 148 | 149 | from plan_lint.types import ErrorCode, Plan, PlanError, Policy 150 | 151 | def check_plan(plan: Plan, policy: Policy) -> List[PlanError]: 152 | errors = [] 153 | 154 | for i, step in enumerate(plan.steps): 155 | if step.tool.startswith("sql.") and "query" in step.args: 156 | query = step.args["query"].upper() 157 | write_keywords = ["INSERT", "UPDATE", "DELETE"] 158 | 159 | for keyword in write_keywords: 160 | if keyword in query: 161 | errors.append( 162 | PlanError( 163 | step=i, 164 | code=ErrorCode.TOOL_DENY, 165 | msg=f"SQL query contains write operation '{keyword}'", 166 | ) 167 | ) 168 | 169 | return errors 170 | ``` 171 | 172 | ## 🛡️ Built for: 173 | • LLM-based Agents (LangGraph, Autogen, CrewAI) 174 | • Reasoning Engines (Tree of Thought, CoT, ReAct, DEPS) 175 | • Custom AI Workflows (internal agent systems) 176 | • Enterprise LLM Deployments (risk & compliance sensitive) 177 | 178 | ## 🧩 Extending plan-lint 179 | 180 | Want to create your own checks? 181 | • Fork the repo 182 | • Add new rule modules inside /rules 183 | • Register the rule in rule_registry.py 184 | 185 | Check out the [Developer Guide](https://cirbuk.github.io/plan-lint/) . 186 | 187 | ## 🤝 Contributing 188 | 189 | We welcome contributions from the community! To get started: 190 | 191 | 1. Check the [open issues](https://github.com/cirbuk/plan-lint/issues) or create a new one to discuss your ideas 192 | 2. Fork the repository 193 | 3. Make your changes following our [contribution guidelines](CONTRIBUTING.md) 194 | 4. Submit a pull request 195 | 196 | Please read our [Code of Conduct](CODE_OF_CONDUCT.md) to keep our community approachable and respectable. 197 | 198 | ## 🏗️ Development 199 | 200 | To set up a development environment: 201 | 202 | ```bash 203 | # Clone the repository 204 | git clone https://github.com/cirbuk/plan-lint.git 205 | cd plan-lint 206 | 207 | # Create a virtual environment 208 | python -m venv .venv 209 | source .venv/bin/activate # On Windows: .venv\Scripts\activate 210 | 211 | # Install development dependencies 212 | pip install -e ".[dev]" 213 | 214 | # Install pre-commit hooks 215 | pre-commit install 216 | ``` 217 | 218 | ## 🌟 If you like this project… 219 | 220 | Please star this repo! 221 | It helps others discover the project and contributes to safer AI systems globally. 222 | Together, we can build trustworthy agentic infrastructures. 💬 223 | 224 | ## 🛠️ Roadmap 225 | • Auto-Fix simple errors 226 | • VS Code extension for live linting 227 | • GitHub Action for Plan Safety in CI/CD 228 | • Plan Complexity Scorer 229 | • Enterprise Mode (fine-grained custom policy linting) 230 | 231 | 232 | ## 📄 License 233 | 234 | This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Currently, we provide security updates for these versions: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 0.0.1 | :white_check_mark: | 10 | 11 | ## Reporting a Vulnerability 12 | 13 | We take the security of Plan-Linter seriously. If you believe you've found a security vulnerability, please follow these steps: 14 | 15 | 1. **Do NOT disclose the vulnerability publicly** (no GitHub issues for security vulnerabilities) 16 | 2. Email us at security@your-organization.com with details about the vulnerability 17 | 3. Include steps to reproduce, impact, and any potential mitigations you've identified 18 | 4. We will acknowledge receipt of your report within 48 hours 19 | 20 | ## What to Expect 21 | 22 | - We'll acknowledge your email within 48 hours 23 | - We'll provide an initial assessment of the report within 7 days 24 | - We'll work with you to understand and validate the issue 25 | - We'll develop and release a fix according to our severity assessment 26 | - We'll publicly disclose the issue after a fix is available (crediting you if desired) 27 | 28 | ## Security Best Practices for Users 29 | 30 | - Keep Plan-Linter updated to the latest version 31 | - Carefully review policy files before using them in production 32 | - Use dedicated service accounts with appropriate permissions when integrating Plan-Linter 33 | - Regularly audit your agent plans for security issues -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO: Plan-Lint Enhancements 2 | 3 | This document outlines planned enhancements and improvements for the plan-lint project. 4 | 5 | ## Policy Framework Enhancements 6 | 7 | - [ ] **Policy Engine Improvements** 8 | - [ ] Clarify separation between core engine and domain-specific policies 9 | - [ ] Add support for custom policy functions beyond basic rules 10 | - [ ] Provide extensible policy templates to help users get started 11 | - [ ] Build validation metrics to identify most triggered policy rules 12 | 13 | - [x] **Policy Authoring Tools** 14 | - [x] Create a policy linting system to validate policy correctness 15 | - [x] Implement a policy testing framework to verify policy behavior 16 | - [x] Add policy registration mechanism for managing multiple policies 17 | - [x] Build documentation generator for policies 18 | 19 | - [x] **OPA/Rego Integration** 20 | - [x] Add support for Open Policy Agent (OPA) policies written in Rego 21 | - [x] Create translators between YAML policies and Rego policies 22 | - [x] Implement Rego evaluation engine adapter 23 | - [x] Add examples of Rego policy patterns (without domain-specific content) 24 | 25 | - [ ] **Pluggable Storage Backend** 26 | - [ ] Create interface for policy storage backends 27 | - [ ] Implement file system storage provider 28 | - [ ] Add support for database storage (SQL, MongoDB) 29 | - [ ] Implement cloud storage providers (S3, Azure Blob, GCS) 30 | - [ ] Add versioning and rollback capabilities for policies 31 | 32 | ## Performance Improvements 33 | 34 | - [ ] **Performance Optimizations** 35 | - [ ] Implement batch validation to handle multiple plans concurrently 36 | - [ ] Add caching for frequently validated plan patterns 37 | - [ ] Profile and optimize regex matching for better performance with large plans 38 | - [ ] Investigate GPU acceleration for large-scale validation 39 | 40 | ## Integration Enhancements 41 | 42 | - [ ] **Framework Integration** 43 | - [ ] Create SDK adapters for popular agent frameworks (LangChain, AutoGPT, CrewAI) 44 | - [ ] Build CI/CD plugins for GitHub Actions, GitLab CI, etc. 45 | - [ ] Develop a standalone web service with REST API for remote validation 46 | 47 | - [ ] **Security Incident Reporting** 48 | - [ ] Implement a reporting mechanism for detected security issues 49 | - [ ] Create integration with SIEM systems 50 | - [ ] Add logging compatibility with popular security monitoring tools 51 | - [ ] Develop threat intelligence sharing capabilities 52 | - [ ] Create customizable alerting system for critical violations 53 | 54 | ## User Experience 55 | 56 | - [ ] **Visualization and Reporting** 57 | - [ ] Create a web UI dashboard for visualizing plan validation results 58 | - [ ] Add report export functionality (PDF, HTML, JSON) 59 | - [ ] Implement historical validation tracking for identifying patterns over time 60 | - [ ] Add visual indicators of risk severity and policy compliance 61 | 62 | - [ ] **Advanced Validation Features** 63 | - [ ] Add natural language explanations of why plans were rejected 64 | - [ ] Implement automatic plan repair suggestions to fix security issues 65 | - [ ] Create differential validation to compare plan changes 66 | - [ ] Add plan simulation capabilities to test execution outcomes 67 | 68 | ## Documentation and Testing 69 | 70 | - [x] **Extended Documentation** 71 | - [x] Create a comprehensive tutorial series on policy authoring 72 | - [x] Document policy engine extension points for custom integrations 73 | - [x] Develop animated visualizations of the validation process 74 | - [x] Create policy authoring guidelines and best practices 75 | 76 | - [ ] **Testing Enhancements** 77 | - [ ] Expand test coverage with more edge cases 78 | - [ ] Create a validation benchmark suite with known-vulnerable plans 79 | - [ ] Implement property-based testing for policy engine 80 | - [ ] Add continuous fuzzing for validation functions 81 | 82 | ## Advanced Features 83 | 84 | - [ ] **Ecosystem Tools** 85 | - [ ] Build a policy generator wizard to help users create policies 86 | - [ ] Create a web-based playground for testing policies against sample plans 87 | - [ ] Develop a VS Code extension for in-editor policy authoring and testing 88 | 89 | - [ ] **Machine Learning Enhancements** 90 | - [ ] Train a model to identify potentially risky patterns not covered by explicit policies 91 | - [ ] Implement anomaly detection for unusual plan structures 92 | - [ ] Build adaptive risk scoring based on historical validation data 93 | 94 | - [ ] **Compliance Helpers** 95 | - [ ] Add example policy patterns for common compliance requirements (without full implementation) 96 | - [ ] Create compliance documentation helpers 97 | - [ ] Implement policy coverage analysis for compliance requirements 98 | 99 | ## Implementation Priorities 100 | 101 | **Short-term (1-3 months):** 102 | - ✅ OPA/Rego integration 103 | - ✅ Policy authoring tools (linting, testing, registration) 104 | - [ ] Basic security incident reporting 105 | - [ ] Pluggable storage backend for policies 106 | 107 | **Medium-term (3-6 months):** 108 | - [ ] Advanced validation features 109 | - [ ] Framework integrations 110 | - [ ] Performance optimizations 111 | - ✅ Extended documentation 112 | 113 | **Long-term (6+ months):** 114 | - [ ] Machine learning enhancements 115 | - [ ] Compliance helpers 116 | - [ ] Ecosystem tools 117 | - [ ] Web UI dashboard -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Plan-Lint SDK 2 | 3 | This directory contains the documentation for Plan-Lint, a static analysis toolkit for validating LLM agent plans before execution. 4 | 5 | ## Documentation Structure 6 | 7 | The documentation is organized into the following sections: 8 | 9 | - **Introduction** (`index.md`): Overview of Plan-Lint 10 | - **Getting Started** (`getting-started.md`): Installation and basic usage 11 | - **Policy Authoring Guide** (`policy-authoring.md`): Writing policies for Plan-Lint 12 | - **Examples**: Real-world examples of using Plan-Lint 13 | - **Documentation**: Detailed guides on various aspects of Plan-Lint 14 | - **API Reference**: Detailed information about the Plan-Lint API 15 | - **Advanced**: Advanced configurations and integrations 16 | 17 | ## Building the Documentation 18 | 19 | To build and serve the documentation locally: 20 | 21 | ```bash 22 | # Install the package with documentation dependencies 23 | pip install -e ".[docs]" 24 | 25 | # Serve the documentation (with live reload) 26 | make serve-docs 27 | # OR 28 | mkdocs serve 29 | 30 | # Build the static site 31 | make build-docs 32 | # OR 33 | mkdocs build 34 | ``` 35 | 36 | The documentation is built using [MkDocs](https://www.mkdocs.org/) with the [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) theme. 37 | 38 | ## Contributing to the Documentation 39 | 40 | We welcome contributions to improve the documentation! Here are some guidelines: 41 | 42 | 1. **File Locations**: Documentation files should be placed in the appropriate directory based on their category: 43 | - Example files go in `docs/examples/` 44 | - API documentation goes in `docs/api/` 45 | - General documentation goes in `docs/documentation/` 46 | - Advanced topics go in `docs/advanced/` 47 | 48 | 2. **Navigation**: The navigation structure is defined in `mkdocs.yml`. If you add a new file, update the `nav` section in `mkdocs.yml` to include it. 49 | 50 | 3. **Style Guidelines**: 51 | - Use clear, concise language 52 | - Include code examples where appropriate 53 | - Use headings to organize content 54 | - Add links to related documentation 55 | 56 | 4. **Testing**: After making changes, build the documentation locally to make sure it looks as expected. 57 | 58 | 5. **Cleanup**: After completing your changes, run `make cleanup-docs` to remove any duplicate documentation files. 59 | 60 | ## Documentation TODO 61 | 62 | The following areas of documentation still need to be improved: 63 | 64 | 1. Expand API reference with more details and examples 65 | 2. Add more real-world examples for different use cases 66 | 3. Improve advanced integration guides 67 | 4. Add more diagrams and visual aids 68 | 69 | If you'd like to contribute to any of these areas, please submit a pull request! -------------------------------------------------------------------------------- /docs/advanced/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This guide explains how to contribute to the Plan-Lint project. 4 | 5 | ## Why Contribute? 6 | 7 | Contributing to Plan-Lint helps: 8 | - Improve security of AI agent systems 9 | - Add new validation capabilities 10 | - Fix bugs and enhance existing features 11 | - Share your expertise with the community 12 | - Shape the future of agent safety 13 | 14 | ## Ways to Contribute 15 | 16 | There are many ways to contribute to Plan-Lint: 17 | 18 | 1. **Report Issues**: Report bugs, request features, or suggest improvements 19 | 2. **Improve Documentation**: Fix errors, add examples, or clarify explanations 20 | 3. **Develop Code**: Add features, fix bugs, or improve performance 21 | 4. **Share Policies**: Contribute policy examples for specific use cases 22 | 5. **Spread the Word**: Share your experience with Plan-Lint 23 | 24 | ## Development Environment Setup 25 | 26 | ### Prerequisites 27 | 28 | - Python 3.9 or later 29 | - Git 30 | - A GitHub account 31 | 32 | ### Clone and Set Up the Repository 33 | 34 | ```bash 35 | # Clone the repository 36 | git clone https://github.com/yourusername/plan-lint.git 37 | cd plan-lint 38 | 39 | # Create a virtual environment 40 | python -m venv venv 41 | source venv/bin/activate # On Windows: venv\Scripts\activate 42 | 43 | # Install development dependencies 44 | pip install -e ".[dev]" 45 | ``` 46 | 47 | ### Run Tests 48 | 49 | ```bash 50 | # Run unit tests 51 | pytest 52 | 53 | # Run with coverage 54 | pytest --cov=plan_lint tests/ 55 | ``` 56 | 57 | ## Contribution Workflow 58 | 59 | ### 1. Choose an Issue 60 | 61 | - Browse the [issue tracker](https://github.com/yourusername/plan-lint/issues) 62 | - Look for issues labeled `good first issue` if you're new 63 | - Comment on an issue to indicate you're working on it 64 | 65 | ### 2. Create a Branch 66 | 67 | ```bash 68 | # Update your main branch 69 | git checkout main 70 | git pull origin main 71 | 72 | # Create a new branch 73 | git checkout -b feature/your-feature-name 74 | ``` 75 | 76 | ### 3. Make Changes 77 | 78 | - Write code following the style guidelines 79 | - Add tests for new features 80 | - Update documentation as needed 81 | 82 | ### 4. Test Your Changes 83 | 84 | ```bash 85 | # Run tests to ensure everything works 86 | pytest 87 | ``` 88 | 89 | ### 5. Submit a Pull Request 90 | 91 | ```bash 92 | # Push your branch to your fork 93 | git push origin feature/your-feature-name 94 | ``` 95 | 96 | Then create a Pull Request on GitHub: 97 | 1. Go to the original repository 98 | 2. Click "New Pull Request" 99 | 3. Select your branch 100 | 4. Fill in the Pull Request template 101 | 102 | ## Code Style Guidelines 103 | 104 | Plan-Lint follows these style guidelines: 105 | 106 | - **PEP 8**: Follow Python's style guide 107 | - **Type Hints**: Use type hints for function parameters and return values 108 | - **Docstrings**: Document classes and functions with docstrings 109 | - **Commit Messages**: Write clear, concise commit messages 110 | 111 | We use the following tools to enforce style: 112 | 113 | ```bash 114 | # Run code formatters 115 | black plan_lint tests 116 | 117 | # Run linters 118 | flake8 plan_lint tests 119 | mypy plan_lint 120 | ``` 121 | 122 | ## Adding New Features 123 | 124 | ### New Validators 125 | 126 | To add a new validator: 127 | 128 | 1. Create a new file in `plan_lint/validators/` 129 | 2. Implement the validator class extending `BaseValidator` 130 | 3. Register your validator in `plan_lint/validators/__init__.py` 131 | 4. Add tests in `tests/validators/` 132 | 5. Update documentation in `docs/` 133 | 134 | Example validator structure: 135 | 136 | ```python 137 | from plan_lint.validators.base import BaseValidator, ValidationResult 138 | 139 | class MyCustomValidator(BaseValidator): 140 | """Validator that checks for my custom condition.""" 141 | 142 | def validate(self, plan, context=None): 143 | """Validate the plan.""" 144 | violations = [] 145 | 146 | # Implement validation logic 147 | for step in plan.get("steps", []): 148 | if self._check_violation(step): 149 | violations.append({ 150 | "rule": "my_custom_rule", 151 | "message": "Description of the violation", 152 | "severity": "medium", 153 | "step_id": step.get("id") 154 | }) 155 | 156 | return ValidationResult(violations) 157 | 158 | def _check_violation(self, step): 159 | """Helper method to check for violations.""" 160 | # Implement check logic 161 | return False 162 | ``` 163 | 164 | ### New Rule Types 165 | 166 | To add a new rule type: 167 | 168 | 1. Update `plan_lint/rules/` 169 | 2. Add parser and validation logic 170 | 3. Update the schema in `plan_lint/schemas/` 171 | 4. Add tests in `tests/rules/` 172 | 5. Update documentation with examples 173 | 174 | ## Writing Tests 175 | 176 | Plan-Lint uses pytest for testing. Follow these guidelines: 177 | 178 | - Test each feature and edge case 179 | - Use fixtures for reusable test data 180 | - Structure tests following the project's organization 181 | - Name tests descriptively (`test_should_detect_sql_injection`) 182 | 183 | Example test: 184 | 185 | ```python 186 | import pytest 187 | from plan_lint import validate_plan 188 | from plan_lint.loader import load_policy 189 | 190 | @pytest.fixture 191 | def vulnerable_plan(): 192 | return { 193 | "steps": [ 194 | { 195 | "id": "step1", 196 | "tool": "db.query", 197 | "parameters": { 198 | "query": "SELECT * FROM users WHERE username = 'admin' OR 1=1" 199 | } 200 | } 201 | ] 202 | } 203 | 204 | def test_should_detect_sql_injection(vulnerable_plan): 205 | policy = load_policy("tests/fixtures/sql_injection_policy.yaml") 206 | result = validate_plan(vulnerable_plan, policy) 207 | 208 | assert not result.is_valid 209 | assert len(result.violations) == 1 210 | assert result.violations[0].rule == "sql_injection" 211 | ``` 212 | 213 | ## Updating Documentation 214 | 215 | Documentation is crucial for Plan-Lint. When making changes: 216 | 217 | 1. Update relevant documentation files in `docs/` 218 | 2. Add examples for new features 219 | 3. Ensure code examples work correctly 220 | 4. Check for clarity and correctness 221 | 222 | ## Release Process 223 | 224 | The release process follows these steps: 225 | 226 | 1. Update version in `setup.py` and `plan_lint/__init__.py` 227 | 2. Update `CHANGELOG.md` with new changes 228 | 3. Create a pull request for the release 229 | 4. After approval, merge to main 230 | 5. Create a new release on GitHub 231 | 6. CI/CD will publish to PyPI 232 | 233 | ## Community Guidelines 234 | 235 | When contributing to Plan-Lint: 236 | 237 | - Be respectful and inclusive 238 | - Provide constructive feedback 239 | - Help others with their contributions 240 | - Follow the code of conduct 241 | 242 | ## Getting Help 243 | 244 | If you need help with your contribution: 245 | 246 | - Ask questions in the issue you're working on 247 | - Join the community discussion forum 248 | - Check existing documentation and examples 249 | 250 | Thank you for contributing to Plan-Lint! Your efforts help make AI agent systems safer and more secure. -------------------------------------------------------------------------------- /docs/api/core.md: -------------------------------------------------------------------------------- 1 | # Core API 2 | 3 | This page documents the core functions of Plan-Lint. 4 | 5 | ## `validate_plan` 6 | 7 | The main function for validating agent plans. 8 | 9 | ```python 10 | from plan_lint.core import validate_plan 11 | 12 | result = validate_plan( 13 | plan, 14 | policy, 15 | rego_policy=None, 16 | use_opa=False 17 | ) 18 | ``` 19 | 20 | ### Parameters 21 | 22 | | Parameter | Type | Description | 23 | |-----------|------|-------------| 24 | | `plan` | `Plan` | The agent plan to validate | 25 | | `policy` | `Policy` | Policy object containing validation rules | 26 | | `rego_policy` | `Optional[str]` | Optional Rego policy as a string | 27 | | `use_opa` | `bool` | Whether to use OPA for validation (defaults to False) | 28 | 29 | ### Returns 30 | 31 | Returns a `ValidationResult` object containing: 32 | 33 | | Attribute | Type | Description | 34 | |-----------|------|-------------| 35 | | `status` | `Status` | Status of validation (PASS, WARN, ERROR) | 36 | | `risk_score` | `float` | Risk score between 0.0 and 1.0 | 37 | | `errors` | `List[PlanError]` | List of validation errors | 38 | | `warnings` | `List[PlanWarning]` | List of validation warnings | 39 | 40 | ## `calculate_risk_score` 41 | 42 | Calculate a risk score for the plan based on errors and warnings. 43 | 44 | ```python 45 | from plan_lint.core import calculate_risk_score 46 | 47 | risk_score = calculate_risk_score(errors, warnings, risk_weights) 48 | ``` 49 | 50 | ### Parameters 51 | 52 | | Parameter | Type | Description | 53 | |-----------|------|-------------| 54 | | `errors` | `List[PlanError]` | List of errors found during validation | 55 | | `warnings` | `List[PlanWarning]` | List of warnings found during validation | 56 | | `risk_weights` | `Dict[str, float]` | Dictionary mapping error/warning types to weights | 57 | 58 | ### Returns 59 | 60 | Returns a float between 0.0 and 1.0 representing the risk score. 61 | 62 | ## Example Usage 63 | 64 | ```python 65 | from plan_lint.core import validate_plan, calculate_risk_score 66 | from plan_lint.loader import load_plan, load_policy 67 | from plan_lint.types import Status, PlanError, ErrorCode 68 | 69 | # Basic validation example 70 | plan = load_plan("plans/customer_refund.json") 71 | policy, rego_policy = load_policy("policies/security.yaml") 72 | 73 | # Validate plan 74 | result = validate_plan(plan, policy) 75 | 76 | # Check results 77 | if result.status == Status.PASS: 78 | print("Plan is valid!") 79 | else: 80 | print(f"Plan validation failed with risk score: {result.risk_score}") 81 | for error in result.errors: 82 | print(f"Step {error.step}: {error.msg} ({error.code})") 83 | 84 | # Manual risk score calculation 85 | errors = [ 86 | PlanError(step=1, code=ErrorCode.RAW_SECRET, msg="Sensitive data detected"), 87 | PlanError(step=2, code=ErrorCode.BOUND_VIOLATION, msg="Amount exceeds maximum") 88 | ] 89 | warnings = [] 90 | risk_weights = { 91 | "raw_secret": 0.7, 92 | "bound_violation": 0.4 93 | } 94 | 95 | risk_score = calculate_risk_score(errors, warnings, risk_weights) 96 | print(f"Risk score: {risk_score}") 97 | ``` 98 | -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- 1 | # API Reference Overview 2 | 3 | This section provides detailed information about the Plan-Lint API. 4 | 5 | ## API Sections 6 | 7 | The Plan-Lint API is organized into the following sections: 8 | 9 | - **[Core](core.md)**: Core functions for validating plans 10 | - **[Types](types.md)**: Data types for representing plans, steps, policies, and validation results 11 | - **[Loader](loader.md)**: Functions for loading plans, policies, and schemas 12 | - **[Rules](rules.md)**: Rule validation functions for checking specific aspects of plans 13 | - **[Validator](validator.md)**: Reusable validator class for validating plans against policies 14 | 15 | ## Quick Start 16 | 17 | Here's a quick example to get you started with the Plan-Lint API: 18 | 19 | ```python 20 | from plan_lint.core import validate_plan 21 | from plan_lint.loader import load_plan, load_policy 22 | from plan_lint.types import Status 23 | 24 | # Load plan and policy 25 | plan = load_plan("plans/customer_refund.json") 26 | policy, rego_policy = load_policy("policies/security.yaml") 27 | 28 | # Validate plan 29 | result = validate_plan(plan, policy) 30 | 31 | # Check results 32 | if result.status == Status.PASS: 33 | print("Plan is valid!") 34 | else: 35 | print(f"Plan validation failed with risk score: {result.risk_score}") 36 | for error in result.errors: 37 | print(f"Step {error.step}: {error.msg} ({error.code})") 38 | ``` 39 | 40 | ## Installation 41 | 42 | To use the Plan-Lint API, first install the package: 43 | 44 | ```bash 45 | pip install plan-lint 46 | ``` 47 | 48 | ## Python Version Compatibility 49 | 50 | Plan-Lint requires Python 3.8 or later. 51 | 52 | ## Error Handling 53 | 54 | Plan-Lint functions raise exceptions in the following cases: 55 | 56 | - `ValueError`: Invalid plan or policy structure 57 | - `FileNotFoundError`: Referenced plan or policy file not found 58 | - `jsonschema.exceptions.ValidationError`: Plan schema validation failure 59 | 60 | Always handle these exceptions in production code: 61 | 62 | ```python 63 | from plan_lint.loader import load_plan, load_policy 64 | from plan_lint.core import validate_plan 65 | import jsonschema 66 | 67 | try: 68 | plan = load_plan("plans/customer_refund.json") 69 | policy, rego_policy = load_policy("policies/security.yaml") 70 | result = validate_plan(plan, policy) 71 | 72 | # Process result... 73 | 74 | except FileNotFoundError as e: 75 | print(f"File not found: {e}") 76 | except ValueError as e: 77 | print(f"Invalid plan or policy: {e}") 78 | except jsonschema.exceptions.ValidationError as e: 79 | print(f"Plan schema validation failed: {e}") 80 | except Exception as e: 81 | print(f"Unexpected error: {e}") 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/api/loader.md: -------------------------------------------------------------------------------- 1 | # Loader API 2 | 3 | This page documents the loader functions for loading plans, policies, and schemas. 4 | 5 | ## `load_plan` 6 | 7 | Load a plan from a JSON file. 8 | 9 | ```python 10 | from plan_lint.loader import load_plan 11 | 12 | plan = load_plan("path/to/plan.json") 13 | ``` 14 | 15 | ### Parameters 16 | 17 | | Parameter | Type | Description | 18 | |-----------|------|-------------| 19 | | `plan_path` | `str` | Path to a JSON plan file | 20 | 21 | ### Returns 22 | 23 | Returns a `Plan` object. 24 | 25 | ## `load_policy` 26 | 27 | Load a policy from a YAML or Rego file. 28 | 29 | ```python 30 | from plan_lint.loader import load_policy 31 | 32 | policy, rego_policy = load_policy("path/to/policy.yaml") 33 | # or 34 | policy, rego_policy = load_policy("path/to/policy.rego") 35 | ``` 36 | 37 | ### Parameters 38 | 39 | | Parameter | Type | Description | 40 | |-----------|------|-------------| 41 | | `policy_path` | `Optional[str]` | Path to a policy file (YAML or Rego) | 42 | 43 | ### Returns 44 | 45 | Returns a tuple of (`Policy` object, Optional Rego policy string). 46 | 47 | ## `load_yaml_policy` 48 | 49 | Load a policy specifically from a YAML file. 50 | 51 | ```python 52 | from plan_lint.loader import load_yaml_policy 53 | 54 | policy = load_yaml_policy("path/to/policy.yaml") 55 | ``` 56 | 57 | ### Parameters 58 | 59 | | Parameter | Type | Description | 60 | |-----------|------|-------------| 61 | | `policy_path` | `str` | Path to a YAML policy file | 62 | 63 | ### Returns 64 | 65 | Returns a `Policy` object. 66 | 67 | ## `load_rego_policy` 68 | 69 | Load a Rego policy from a file. 70 | 71 | ```python 72 | from plan_lint.loader import load_rego_policy 73 | 74 | rego_policy = load_rego_policy("path/to/policy.rego") 75 | ``` 76 | 77 | ### Parameters 78 | 79 | | Parameter | Type | Description | 80 | |-----------|------|-------------| 81 | | `policy_path` | `str` | Path to a Rego policy file | 82 | 83 | ### Returns 84 | 85 | Returns the Rego policy as a string. 86 | 87 | ## `load_schema` 88 | 89 | Load a JSON schema for plan validation. 90 | 91 | ```python 92 | from plan_lint.loader import load_schema 93 | 94 | schema = load_schema() # Use default schema 95 | # or 96 | schema = load_schema("path/to/custom/schema.json") 97 | ``` 98 | 99 | ### Parameters 100 | 101 | | Parameter | Type | Description | 102 | |-----------|------|-------------| 103 | | `schema_path` | `Optional[str]` | Path to a JSON schema file (None for default) | 104 | 105 | ### Returns 106 | 107 | Returns the schema as a dictionary. 108 | 109 | ## Example Usage 110 | 111 | ```python 112 | from plan_lint.loader import load_plan, load_policy 113 | from plan_lint.core import validate_plan 114 | 115 | # Load plan and policy 116 | plan = load_plan("plans/customer_refund.json") 117 | policy, rego_policy = load_policy("policies/security.yaml") 118 | 119 | # Validate plan 120 | result = validate_plan(plan, policy) 121 | 122 | # For a Rego policy 123 | policy, rego_policy = load_policy("policies/security.rego") 124 | result = validate_plan(plan, policy, rego_policy=rego_policy, use_opa=True) 125 | ``` 126 | -------------------------------------------------------------------------------- /docs/api/reference.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | This document provides detailed information about the Plan-Lint API, including the main functions, classes, and their parameters. 4 | 5 | ## Core Functions 6 | 7 | ### `validate_plan` 8 | 9 | The primary function for validating plans against policies. 10 | 11 | ```python 12 | def validate_plan( 13 | plan: Dict[str, Any], 14 | policies: Optional[List[str]] = None, 15 | context: Optional[Dict[str, Any]] = None, 16 | config: Optional[Dict[str, Any]] = None 17 | ) -> ValidationResult: 18 | """ 19 | Validate a plan against policies. 20 | 21 | Args: 22 | plan: The plan to validate, containing steps and their tools/parameters 23 | policies: Optional list of paths to policy files. If None, uses default policies 24 | context: Optional context information to provide to the policies 25 | config: Optional configuration for the validation process 26 | 27 | Returns: 28 | A ValidationResult object containing validation results 29 | """ 30 | ``` 31 | 32 | ### `load_policy` 33 | 34 | Load a Rego policy from a file. 35 | 36 | ```python 37 | def load_policy( 38 | policy_path: str 39 | ) -> str: 40 | """ 41 | Load a Rego policy file. 42 | 43 | Args: 44 | policy_path: Path to the Rego policy file 45 | 46 | Returns: 47 | The policy content as a string 48 | 49 | Raises: 50 | FileNotFoundError: If the policy file doesn't exist 51 | """ 52 | ``` 53 | 54 | ### `format_plan` 55 | 56 | Format a plan to ensure it meets the expected structure for validation. 57 | 58 | ```python 59 | def format_plan( 60 | plan: Dict[str, Any] 61 | ) -> Dict[str, Any]: 62 | """ 63 | Format a plan to ensure it has the expected structure. 64 | 65 | Args: 66 | plan: The plan to format 67 | 68 | Returns: 69 | The formatted plan 70 | """ 71 | ``` 72 | 73 | ## Classes 74 | 75 | ### `ValidationResult` 76 | 77 | Contains the results of plan validation. 78 | 79 | ```python 80 | class ValidationResult: 81 | """ 82 | Result of a plan validation. 83 | 84 | Attributes: 85 | valid (bool): Whether the plan is valid according to all policies 86 | violations (List[PolicyViolation]): List of policy violations found 87 | details (Dict[str, Any]): Additional details about the validation 88 | 89 | Methods: 90 | to_dict(): Convert the result to a dictionary 91 | to_json(): Convert the result to a JSON string 92 | """ 93 | 94 | @property 95 | def valid(self) -> bool: 96 | """Whether the plan is valid (no violations).""" 97 | 98 | @property 99 | def violations(self) -> List["PolicyViolation"]: 100 | """List of policy violations.""" 101 | 102 | def to_dict(self) -> Dict[str, Any]: 103 | """Convert the result to a dictionary.""" 104 | 105 | def to_json(self, **kwargs) -> str: 106 | """Convert the result to a JSON string.""" 107 | ``` 108 | 109 | ### `PolicyViolation` 110 | 111 | Represents a violation of a policy rule. 112 | 113 | ```python 114 | class PolicyViolation: 115 | """ 116 | Represents a violation of a policy rule. 117 | 118 | Attributes: 119 | rule (str): The policy rule that was violated 120 | message (str): Description of the violation 121 | severity (str): Severity level ('low', 'medium', 'high', 'critical') 122 | category (str): Category of the violation (e.g., 'security', 'privacy') 123 | step_id (Optional[str]): ID of the step that caused the violation 124 | metadata (Dict[str, Any]): Additional metadata about the violation 125 | """ 126 | 127 | @property 128 | def rule(self) -> str: 129 | """The policy rule that was violated.""" 130 | 131 | @property 132 | def message(self) -> str: 133 | """Description of the violation.""" 134 | 135 | @property 136 | def severity(self) -> str: 137 | """Severity level of the violation.""" 138 | 139 | @property 140 | def category(self) -> str: 141 | """Category of the violation.""" 142 | 143 | @property 144 | def step_id(self) -> Optional[str]: 145 | """ID of the step that caused the violation, if applicable.""" 146 | 147 | @property 148 | def metadata(self) -> Dict[str, Any]: 149 | """Additional metadata about the violation.""" 150 | ``` 151 | 152 | ### `PolicyEngine` 153 | 154 | Manages policy evaluation using the Open Policy Agent. 155 | 156 | ```python 157 | class PolicyEngine: 158 | """ 159 | Engine for evaluating Rego policies against plans. 160 | 161 | Methods: 162 | evaluate(plan, policies, context): Evaluate policies against a plan 163 | """ 164 | 165 | def evaluate( 166 | self, 167 | plan: Dict[str, Any], 168 | policies: List[str], 169 | context: Optional[Dict[str, Any]] = None 170 | ) -> Dict[str, Any]: 171 | """ 172 | Evaluate policies against a plan. 173 | 174 | Args: 175 | plan: The plan to evaluate 176 | policies: List of policy file paths 177 | context: Optional context information 178 | 179 | Returns: 180 | Evaluation results as a dictionary 181 | """ 182 | ``` 183 | 184 | ## CLI Commands 185 | 186 | ### `plan-lint validate` 187 | 188 | Command-line interface for validating plans. 189 | 190 | ``` 191 | Usage: plan-lint validate [OPTIONS] PLAN_FILE 192 | 193 | Validate a plan against policies. 194 | 195 | Options: 196 | --policies PATH... Custom policy files to use 197 | --context FILE JSON file containing context information 198 | --output FORMAT Output format (text, json, yaml) [default: text] 199 | --config FILE Configuration file 200 | --help Show this message and exit 201 | ``` 202 | 203 | ### `plan-lint test` 204 | 205 | Command-line interface for testing policies. 206 | 207 | ``` 208 | Usage: plan-lint test [OPTIONS] [TEST_DIR] 209 | 210 | Run policy tests. 211 | 212 | Options: 213 | --policies PATH... Custom policy files to test 214 | --verbose Show detailed test output 215 | --help Show this message and exit 216 | ``` 217 | 218 | ## Constants 219 | 220 | ### Severity Levels 221 | 222 | ```python 223 | class Severity: 224 | """Severity levels for policy violations.""" 225 | 226 | LOW = "low" 227 | MEDIUM = "medium" 228 | HIGH = "high" 229 | CRITICAL = "critical" 230 | ``` 231 | 232 | ### Violation Categories 233 | 234 | ```python 235 | class Category: 236 | """Categories for policy violations.""" 237 | 238 | SECURITY = "security" 239 | PRIVACY = "privacy" 240 | AUTHORIZATION = "authorization" 241 | COMPLIANCE = "compliance" 242 | RESOURCE = "resource" 243 | GENERAL = "general" 244 | ``` 245 | 246 | ## Error Classes 247 | 248 | ### `PolicyError` 249 | 250 | Base class for policy-related errors. 251 | 252 | ```python 253 | class PolicyError(Exception): 254 | """Base class for policy-related errors.""" 255 | ``` 256 | 257 | ### `PolicyLoadError` 258 | 259 | Error raised when a policy cannot be loaded. 260 | 261 | ```python 262 | class PolicyLoadError(PolicyError): 263 | """Raised when a policy cannot be loaded.""" 264 | ``` 265 | 266 | ### `PolicyEvaluationError` 267 | 268 | Error raised when policy evaluation fails. 269 | 270 | ```python 271 | class PolicyEvaluationError(PolicyError): 272 | """Raised when policy evaluation fails.""" 273 | ``` 274 | 275 | ## Utility Functions 276 | 277 | ### `get_default_policies` 278 | 279 | Get the paths to the default policy files. 280 | 281 | ```python 282 | def get_default_policies() -> List[str]: 283 | """ 284 | Get the paths to the default policy files. 285 | 286 | Returns: 287 | List of paths to default policy files 288 | """ 289 | ``` 290 | 291 | ### `load_context` 292 | 293 | Load context information from a file. 294 | 295 | ```python 296 | def load_context(context_path: str) -> Dict[str, Any]: 297 | """ 298 | Load context information from a JSON file. 299 | 300 | Args: 301 | context_path: Path to the context file 302 | 303 | Returns: 304 | Context information as a dictionary 305 | 306 | Raises: 307 | FileNotFoundError: If the context file doesn't exist 308 | json.JSONDecodeError: If the context file is not valid JSON 309 | """ 310 | ``` -------------------------------------------------------------------------------- /docs/api/rules.md: -------------------------------------------------------------------------------- 1 | # Rules API 2 | 3 | This page documents the rule validation functions of Plan-Lint. 4 | 5 | ## Built-in Rule Functions 6 | 7 | Plan-Lint provides several built-in rule functions for validating different aspects of plans. 8 | 9 | ### `check_tools_allowed` 10 | 11 | Check if a step's tool is allowed by the policy. 12 | 13 | ```python 14 | from plan_lint.core import check_tools_allowed 15 | 16 | error = check_tools_allowed(step, allowed_tools, step_idx) 17 | ``` 18 | 19 | ### Parameters 20 | 21 | | Parameter | Type | Description | 22 | |-----------|------|-------------| 23 | | `step` | `PlanStep` | The plan step to check | 24 | | `allowed_tools` | `List[str]` | List of allowed tool names | 25 | | `step_idx` | `int` | Index of the step in the plan | 26 | 27 | ### Returns 28 | 29 | Returns a `PlanError` if the tool is not allowed, `None` otherwise. 30 | 31 | ### `check_bounds` 32 | 33 | Check if a step's arguments are within bounds defined by the policy. 34 | 35 | ```python 36 | from plan_lint.core import check_bounds 37 | 38 | errors = check_bounds(step, bounds, step_idx) 39 | ``` 40 | 41 | ### Parameters 42 | 43 | | Parameter | Type | Description | 44 | |-----------|------|-------------| 45 | | `step` | `PlanStep` | The plan step to check | 46 | | `bounds` | `Dict[str, List[float]]` | Dictionary mapping tool.arg paths to [min, max] bounds | 47 | | `step_idx` | `int` | Index of the step in the plan | 48 | 49 | ### Returns 50 | 51 | Returns a list of `PlanError` for any bounds violations. 52 | 53 | ### `check_raw_secrets` 54 | 55 | Check if a step contains raw secrets or sensitive data. 56 | 57 | ```python 58 | from plan_lint.core import check_raw_secrets 59 | 60 | errors = check_raw_secrets(step, deny_patterns, step_idx) 61 | ``` 62 | 63 | ### Parameters 64 | 65 | | Parameter | Type | Description | 66 | |-----------|------|-------------| 67 | | `step` | `PlanStep` | The plan step to check | 68 | | `deny_patterns` | `List[str]` | List of regex patterns to deny | 69 | | `step_idx` | `int` | Index of the step in the plan | 70 | 71 | ### Returns 72 | 73 | Returns a list of `PlanError` for any detected secrets. 74 | 75 | ### `detect_cycles` 76 | 77 | Detect cycles in the plan's step dependencies. 78 | 79 | ```python 80 | from plan_lint.core import detect_cycles 81 | 82 | error = detect_cycles(plan) 83 | ``` 84 | 85 | ### Parameters 86 | 87 | | Parameter | Type | Description | 88 | |-----------|------|-------------| 89 | | `plan` | `Plan` | The plan to check | 90 | 91 | ### Returns 92 | 93 | Returns a `PlanError` if a cycle is detected, `None` otherwise. 94 | 95 | ## Creating Custom Rule Functions 96 | 97 | You can create custom rule functions to add your own validation logic: 98 | 99 | ```python 100 | from typing import List, Dict, Any, Optional 101 | from plan_lint.types import Plan, PlanStep, PlanError, ErrorCode 102 | 103 | def check_custom_rule( 104 | plan: Plan, 105 | context: Optional[Dict[str, Any]] = None 106 | ) -> List[PlanError]: 107 | """ 108 | Custom rule to validate some aspect of the plan. 109 | 110 | Args: 111 | plan: The plan to validate 112 | context: Optional context information 113 | 114 | Returns: 115 | List of errors found during validation 116 | """ 117 | errors = [] 118 | 119 | # Example: Check that payment operations have an approval step 120 | for i, step in enumerate(plan.steps): 121 | if step.tool.startswith("payments."): 122 | # Look for an approval step that depends on this payment 123 | approval_step_exists = False 124 | for j, other_step in enumerate(plan.steps): 125 | if (other_step.tool == "approval.request" and 126 | step.id in other_step.depends_on): 127 | approval_step_exists = True 128 | break 129 | 130 | if not approval_step_exists: 131 | errors.append( 132 | PlanError( 133 | step=i, 134 | code=ErrorCode.CUSTOM, 135 | msg=f"Payment operation in step {step.id} requires an approval step" 136 | ) 137 | ) 138 | 139 | return errors 140 | ``` 141 | 142 | ## Using Custom Rules 143 | 144 | You can use custom rules with the `validate_plan` function: 145 | 146 | ```python 147 | from plan_lint.core import validate_plan 148 | from my_custom_rules import check_custom_rule 149 | 150 | # Load plan and policy 151 | plan = load_plan("plans/customer_refund.json") 152 | policy, rego_policy = load_policy("policies/security.yaml") 153 | 154 | # Create custom validators list 155 | custom_validators = [check_custom_rule] 156 | 157 | # Validate with custom rules 158 | result = validate_plan( 159 | plan, 160 | policy, 161 | custom_validators=custom_validators, 162 | context={"user_role": "admin"} 163 | ) 164 | ``` 165 | -------------------------------------------------------------------------------- /docs/api/types.md: -------------------------------------------------------------------------------- 1 | # Types API 2 | 3 | This page documents the data types used in Plan-Lint. 4 | 5 | ## `Plan` 6 | 7 | Represents an agent plan to be validated. 8 | 9 | ```python 10 | from plan_lint.types import Plan, PlanStep 11 | 12 | plan = Plan( 13 | goal="Process customer refund", 14 | steps=[ 15 | PlanStep( 16 | id="step1", 17 | tool="db.query", 18 | args={"query": "SELECT * FROM users"} 19 | ), 20 | PlanStep( 21 | id="step2", 22 | tool="notify.email", 23 | args={"to": "user@example.com", "body": "Your refund is processed"} 24 | ) 25 | ], 26 | context={"user_id": "123"} 27 | ) 28 | ``` 29 | 30 | ### Attributes 31 | 32 | | Attribute | Type | Description | 33 | |-----------|------|-------------| 34 | | `goal` | `str` | The goal or purpose of the plan | 35 | | `steps` | `List[PlanStep]` | Steps to be executed in the plan | 36 | | `context` | `Optional[Dict[str, Any]]` | Additional context for the plan | 37 | | `meta` | `Optional[Dict[str, Any]]` | Metadata about the plan | 38 | 39 | ## `PlanStep` 40 | 41 | Represents a single step in an agent plan. 42 | 43 | ```python 44 | from plan_lint.types import PlanStep 45 | 46 | step = PlanStep( 47 | id="step1", 48 | tool="db.query", 49 | args={"query": "SELECT * FROM users"}, 50 | on_fail="abort" 51 | ) 52 | ``` 53 | 54 | ### Attributes 55 | 56 | | Attribute | Type | Description | 57 | |-----------|------|-------------| 58 | | `id` | `str` | Unique identifier for the step | 59 | | `tool` | `str` | The tool to be used in this step | 60 | | `args` | `Dict[str, Any]` | Arguments to pass to the tool | 61 | | `on_fail` | `str` | Action to take if step fails (default: "abort") | 62 | 63 | ## `Policy` 64 | 65 | Represents a policy for plan validation. 66 | 67 | ```python 68 | from plan_lint.types import Policy 69 | 70 | policy = Policy( 71 | allow_tools=["db.query_ro", "notify.email"], 72 | bounds={"payments.transfer.amount": [0.01, 1000.00]}, 73 | deny_tokens_regex=["password", "secret", "DROP TABLE"], 74 | max_steps=10, 75 | risk_weights={"TOOL_DENY": 0.8, "RAW_SECRET": 0.6}, 76 | fail_risk_threshold=0.7 77 | ) 78 | ``` 79 | 80 | ### Attributes 81 | 82 | | Attribute | Type | Description | 83 | |-----------|------|-------------| 84 | | `allow_tools` | `List[str]` | List of allowed tools | 85 | | `bounds` | `Dict[str, List[Union[int, float]]]` | Parameter boundaries | 86 | | `deny_tokens_regex` | `List[str]` | Patterns to reject | 87 | | `max_steps` | `int` | Maximum allowed steps in a plan | 88 | | `risk_weights` | `Dict[str, float]` | Weights for different violation types | 89 | | `fail_risk_threshold` | `float` | Risk threshold for failing validation | 90 | 91 | ## `PlanError` 92 | 93 | Represents an error found during plan validation. 94 | 95 | ```python 96 | from plan_lint.types import PlanError, ErrorCode 97 | 98 | error = PlanError( 99 | step=1, 100 | code=ErrorCode.TOOL_DENY, 101 | msg="Tool 'db.write' is not allowed by policy" 102 | ) 103 | ``` 104 | 105 | ### Attributes 106 | 107 | | Attribute | Type | Description | 108 | |-----------|------|-------------| 109 | | `step` | `Optional[int]` | Index of the step where the error was found | 110 | | `code` | `ErrorCode` | Error code | 111 | | `msg` | `str` | Human-readable error message | 112 | 113 | ## `PlanWarning` 114 | 115 | Represents a warning found during plan validation. 116 | 117 | ```python 118 | from plan_lint.types import PlanWarning 119 | 120 | warning = PlanWarning( 121 | step=1, 122 | code="PERFORMANCE", 123 | msg="This query might be slow for large datasets" 124 | ) 125 | ``` 126 | 127 | ### Attributes 128 | 129 | | Attribute | Type | Description | 130 | |-----------|------|-------------| 131 | | `step` | `Optional[int]` | Index of the step where the warning was found | 132 | | `code` | `str` | Warning code | 133 | | `msg` | `str` | Human-readable warning message | 134 | 135 | ## `ErrorCode` 136 | 137 | Enum of error codes for plan validation failures. 138 | 139 | ```python 140 | from plan_lint.types import ErrorCode 141 | 142 | # Available error codes 143 | ErrorCode.SCHEMA_INVALID # Invalid plan schema 144 | ErrorCode.TOOL_DENY # Unauthorized tool 145 | ErrorCode.BOUND_VIOLATION # Parameter out of bounds 146 | ErrorCode.RAW_SECRET # Sensitive data exposure 147 | ErrorCode.LOOP_DETECTED # Circular dependency detected 148 | ErrorCode.MAX_STEPS_EXCEEDED # Too many steps in plan 149 | ErrorCode.MISSING_HANDLER # Missing error handler 150 | ``` 151 | 152 | ## `Status` 153 | 154 | Enum of validation status values. 155 | 156 | ```python 157 | from plan_lint.types import Status 158 | 159 | Status.PASS # Plan passed validation 160 | Status.WARN # Plan has warnings but passed 161 | Status.ERROR # Plan failed validation 162 | ``` 163 | 164 | ## `ValidationResult` 165 | 166 | Contains the results of plan validation. 167 | 168 | ```python 169 | from plan_lint.types import ValidationResult, Status 170 | 171 | result = ValidationResult( 172 | status=Status.ERROR, 173 | risk_score=0.6, 174 | errors=[error1, error2], 175 | warnings=[warning1] 176 | ) 177 | ``` 178 | 179 | ### Attributes 180 | 181 | | Attribute | Type | Description | 182 | |-----------|------|-------------| 183 | | `status` | `Status` | Status of validation (PASS, WARN, ERROR) | 184 | | `risk_score` | `float` | Risk score between 0.0 and 1.0 | 185 | | `errors` | `List[PlanError]` | List of validation errors | 186 | | `warnings` | `List[PlanWarning]` | List of validation warnings | 187 | -------------------------------------------------------------------------------- /docs/api/validator.md: -------------------------------------------------------------------------------- 1 | # Validator API 2 | 3 | This page documents the policy validator class for reusable validation. 4 | 5 | ## `PolicyValidator` 6 | 7 | Class for creating a reusable validator with specific policies. 8 | 9 | ```python 10 | from plan_lint.validator import PolicyValidator 11 | 12 | validator = PolicyValidator( 13 | policy_files=["policies/security.yaml", "policies/custom.rego"], 14 | custom_validators=[my_custom_validator], 15 | allow_undefined_tools=False 16 | ) 17 | 18 | result = validator.validate(plan, context={"user_role": "admin"}) 19 | ``` 20 | 21 | ### Constructor Parameters 22 | 23 | | Parameter | Type | Description | 24 | |-----------|------|-------------| 25 | | `policy_files` | `List[str]` | List of policy file paths (YAML or Rego) | 26 | | `custom_validators` | `List[Callable]` | Optional list of custom validator functions | 27 | | `allow_undefined_tools` | `bool` | Whether to allow tools not defined in policies | 28 | 29 | ### Methods 30 | 31 | #### `validate(plan, context=None, silent=False)` 32 | 33 | Validate a plan using the configured policies. 34 | 35 | ```python 36 | result = validator.validate(plan, context={"user_role": "admin"}) 37 | ``` 38 | 39 | | Parameter | Type | Description | 40 | |-----------|------|-------------| 41 | | `plan` | `Dict[str, Any]` or `Plan` | The plan to validate | 42 | | `context` | `Dict[str, Any]` | Optional context information for validation | 43 | | `silent` | `bool` | Whether to suppress console output | 44 | 45 | Returns a `ValidationResult` object. 46 | 47 | #### `add_policy_file(file_path)` 48 | 49 | Add a policy file to the validator. 50 | 51 | ```python 52 | validator.add_policy_file("policies/additional.yaml") 53 | ``` 54 | 55 | | Parameter | Type | Description | 56 | |-----------|------|-------------| 57 | | `file_path` | `str` | Path to the policy file to add | 58 | 59 | #### `add_custom_validator(validator_func)` 60 | 61 | Add a custom validator function. 62 | 63 | ```python 64 | validator.add_custom_validator(my_custom_validator) 65 | ``` 66 | 67 | | Parameter | Type | Description | 68 | |-----------|------|-------------| 69 | | `validator_func` | `Callable` | Custom validator function to add | 70 | 71 | ## Example Usage 72 | 73 | ```python 74 | from plan_lint.validator import PolicyValidator 75 | from plan_lint.types import Plan, PlanStep 76 | 77 | # Create a validator with policies 78 | validator = PolicyValidator( 79 | policy_files=["policies/security.yaml", "policies/operations.rego"] 80 | ) 81 | 82 | # Validate a plan 83 | plan = Plan( 84 | goal="Process customer refund", 85 | steps=[ 86 | PlanStep( 87 | id="step1", 88 | tool="db.query_ro", 89 | args={ 90 | "query": "SELECT account FROM customers WHERE id = ?", 91 | "params": ["customer-123"] 92 | } 93 | ), 94 | PlanStep( 95 | id="step2", 96 | tool="payments.transfer", 97 | args={ 98 | "amount": 100.00, 99 | "account": "ACC-123" 100 | } 101 | ) 102 | ] 103 | ) 104 | 105 | # Validate with context 106 | result = validator.validate( 107 | plan, 108 | context={ 109 | "user_role": "admin", 110 | "environment": "production" 111 | } 112 | ) 113 | 114 | # Check results 115 | if result.valid: 116 | print("Plan is valid!") 117 | else: 118 | for error in result.errors: 119 | print(f"Step {error.step}: {error.msg}") 120 | ``` 121 | 122 | ## Web Service Integration 123 | 124 | Example of using PolicyValidator in a web service: 125 | 126 | ```python 127 | from flask import Flask, request, jsonify 128 | from plan_lint.validator import PolicyValidator 129 | 130 | app = Flask(__name__) 131 | 132 | # Create a validator at service startup 133 | validator = PolicyValidator( 134 | policy_files=["policies/security.yaml", "policies/operations.rego"] 135 | ) 136 | 137 | @app.route("/validate", methods=["POST"]) 138 | def validate(): 139 | data = request.json 140 | 141 | plan = data.get("plan") 142 | context = data.get("context", {}) 143 | 144 | if not plan: 145 | return jsonify({"error": "Missing plan"}), 400 146 | 147 | result = validator.validate(plan, context=context) 148 | 149 | return jsonify({ 150 | "valid": result.valid, 151 | "risk_score": result.risk_score, 152 | "errors": [ 153 | { 154 | "step": error.step, 155 | "code": error.code.name, 156 | "message": error.msg 157 | } 158 | for error in result.errors 159 | ] 160 | }) 161 | 162 | if __name__ == "__main__": 163 | app.run(debug=True) 164 | ``` 165 | -------------------------------------------------------------------------------- /docs/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /docs/assets/images/plan-lint-001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cirbuk/plan-lint/55e305f8f4f6732b39b820dc5ac4efa8c1959146/docs/assets/images/plan-lint-001.gif -------------------------------------------------------------------------------- /docs/assets/images/plan-lint-002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cirbuk/plan-lint/55e305f8f4f6732b39b820dc5ac4efa8c1959146/docs/assets/images/plan-lint-002.gif -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This page provides guidelines for contributing to Plan-Lint. 4 | -------------------------------------------------------------------------------- /docs/documentation/index.md: -------------------------------------------------------------------------------- 1 | # Documentation Overview 2 | 3 | Learn how to use and configure Plan-Lint. 4 | -------------------------------------------------------------------------------- /docs/documentation/plan-structure.md: -------------------------------------------------------------------------------- 1 | # Plan Structure 2 | 3 | This page explains the structure of plans that can be validated by Plan-Lint. 4 | 5 | ## Overview 6 | 7 | A plan in Plan-Lint represents a sequence of steps that an AI agent intends to execute. The plan is structured as a JSON object with specific fields that allow Plan-Lint to analyze it for potential security and operational issues. 8 | 9 | ## Plan Format 10 | 11 | Plans are represented as JSON objects with the following structure: 12 | 13 | ```json 14 | { 15 | "goal": "Human-readable description of what the plan aims to accomplish", 16 | "steps": [ 17 | { 18 | "id": "unique-step-identifier", 19 | "tool": "tool_to_execute", 20 | "parameters": { 21 | "param1": "value1", 22 | "param2": "value2" 23 | }, 24 | "on_fail": "abort", 25 | "depends_on": ["previous-step-id"] 26 | } 27 | ], 28 | "context": { 29 | "key1": "value1", 30 | "key2": "value2" 31 | }, 32 | "meta": { 33 | "planner": "model-name", 34 | "created_at": "timestamp" 35 | } 36 | } 37 | ``` 38 | 39 | ### Required Fields 40 | 41 | - **steps**: An array of execution steps that make up the plan (required) 42 | 43 | ### Optional Fields 44 | 45 | - **goal**: A human-readable description of what the plan aims to accomplish 46 | - **context**: Additional context information relevant to the plan 47 | - **meta**: Metadata about the plan such as which model generated it 48 | 49 | ## Step Structure 50 | 51 | Each step in the plan represents an individual action to be executed. Steps have the following structure: 52 | 53 | ```json 54 | { 55 | "id": "step1", 56 | "tool": "tool_name", 57 | "parameters": { 58 | "param1": "value1", 59 | "param2": "value2" 60 | }, 61 | "on_fail": "abort", 62 | "depends_on": ["step0"] 63 | } 64 | ``` 65 | 66 | ### Required Fields 67 | 68 | - **id**: A unique identifier for the step (string) 69 | - **tool**: The name of the tool or function to execute (string) 70 | - **parameters**: An object containing the parameters for the tool execution (object) 71 | 72 | ### Optional Fields 73 | 74 | - **on_fail**: What to do if this step fails (options: "abort", "continue") 75 | - **depends_on**: Array of step IDs that must complete before this step can execute 76 | 77 | ## Parameter References 78 | 79 | Parameters can reference the outputs of previous steps using the syntax `{{step_id.result}}` or `${step_id.result}`. For example: 80 | 81 | ```json 82 | { 83 | "id": "step2", 84 | "tool": "send_email", 85 | "parameters": { 86 | "body": "The account balance is {{step1.result.balance}}", 87 | "to": "${step1.result.email}" 88 | }, 89 | "depends_on": ["step1"] 90 | } 91 | ``` 92 | 93 | This allows steps to use the outputs of previous steps as inputs, creating a workflow. 94 | 95 | ## Special Tool Patterns 96 | 97 | Plan-Lint recognizes special patterns in the tool names to apply specific validations: 98 | 99 | - **db.query**: Database query operations 100 | - **db.query_ro**: Read-only database queries 101 | - **db.write**: Database write operations 102 | - **payments.**: Payment operations (e.g., `payments.transfer`) 103 | - **notify.**: Notification operations (e.g., `notify.email`) 104 | - **file.**: File operations 105 | 106 | These patterns help Plan-Lint apply the appropriate security checks based on the type of operation. 107 | 108 | ## Example Plan 109 | 110 | Here's a complete example of a plan that queries a database and sends an email: 111 | 112 | ```json 113 | { 114 | "goal": "Send monthly account statement to user", 115 | "steps": [ 116 | { 117 | "id": "step1", 118 | "tool": "db.query_ro", 119 | "parameters": { 120 | "query": "SELECT balance, email FROM accounts WHERE user_id = $1", 121 | "args": ["user-123"] 122 | } 123 | }, 124 | { 125 | "id": "step2", 126 | "tool": "notify.email", 127 | "parameters": { 128 | "to": "{{step1.result.email}}", 129 | "subject": "Your Monthly Statement", 130 | "body": "Your current balance is ${{step1.result.balance}}" 131 | }, 132 | "depends_on": ["step1"] 133 | } 134 | ], 135 | "context": { 136 | "user_id": "user-123", 137 | "month": "January 2025" 138 | }, 139 | "meta": { 140 | "planner": "gpt-4o", 141 | "created_at": "2025-01-01T00:00:00Z" 142 | } 143 | } 144 | ``` 145 | 146 | ## Best Practices 147 | 148 | When working with plans, follow these best practices: 149 | 150 | 1. **Use Unique IDs**: Ensure each step has a unique ID. 151 | 2. **Explicit Dependencies**: Always specify step dependencies using the `depends_on` field. 152 | 3. **Minimal Permissions**: Use the most restrictive tool possible (e.g., `db.query_ro` instead of `db.query`). 153 | 4. **Parameter Sanitization**: Ensure user inputs are properly sanitized before including them in step parameters. 154 | 5. **Clear Goal Description**: Include a clear, human-readable goal to make the plan's purpose obvious. 155 | 156 | ## API Usage 157 | 158 | Here's how to create and validate a plan using the Plan-Lint API: 159 | 160 | ```python 161 | from plan_lint import validate_plan 162 | from plan_lint.types import Plan, PlanStep 163 | 164 | # Create a plan programmatically 165 | plan = Plan( 166 | goal="Send notification to user", 167 | steps=[ 168 | PlanStep( 169 | id="step1", 170 | tool="db.query_ro", 171 | parameters={ 172 | "query": "SELECT email FROM users WHERE id = $1", 173 | "args": ["user-456"] 174 | } 175 | ), 176 | PlanStep( 177 | id="step2", 178 | tool="notify.email", 179 | parameters={ 180 | "to": "{{step1.result.email}}", 181 | "subject": "Notification", 182 | "body": "This is a notification" 183 | }, 184 | depends_on=["step1"] 185 | ) 186 | ], 187 | context={"user_id": "user-456"} 188 | ) 189 | 190 | # Validate the plan 191 | result = validate_plan(plan) 192 | 193 | if result.valid: 194 | print("Plan is valid!") 195 | else: 196 | print("Plan validation failed:") 197 | for violation in result.violations: 198 | print(f"- {violation.rule}: {violation.message}") 199 | ``` 200 | -------------------------------------------------------------------------------- /docs/examples/finance-agent-system.md: -------------------------------------------------------------------------------- 1 | # Finance Agent System 2 | 3 | This example demonstrates using Plan-Lint to validate financial transaction plans. 4 | 5 | ## System Overview 6 | 7 | The Finance Agent System is a multi-agent system designed for secure transaction processing. It consists of: 8 | 9 | 1. **Orchestrator Agent**: Coordinates the overall workflow 10 | 2. **Transaction Agent**: Processes financial transactions 11 | 3. **Analysis Agent**: Analyzes transaction patterns 12 | 4. **Plan Validator**: Validates operational plans before execution 13 | 14 | ## Security Concerns 15 | 16 | Financial systems require rigorous security measures. Common vulnerabilities include: 17 | 18 | - SQL injection in transaction queries 19 | - Excessive transaction amounts 20 | - Unauthorized access to accounts 21 | - Sensitive data exposure in logs 22 | 23 | ## Sample Plan 24 | 25 | Here's a sample financial transaction plan: 26 | 27 | ```json 28 | { 29 | "goal": "Process customer refund", 30 | "steps": [ 31 | { 32 | "id": "step1", 33 | "tool": "db.query_ro", 34 | "parameters": { 35 | "query": "SELECT account_balance FROM accounts WHERE id = ?", 36 | "params": ["ACC-123"] 37 | } 38 | }, 39 | { 40 | "id": "step2", 41 | "tool": "payments.transfer", 42 | "parameters": { 43 | "from_account": "COMPANY-MAIN", 44 | "to_account": "ACC-123", 45 | "amount": 500.00, 46 | "reason": "Customer refund" 47 | }, 48 | "depends_on": ["step1"] 49 | }, 50 | { 51 | "id": "step3", 52 | "tool": "notify.email", 53 | "parameters": { 54 | "to": "customer@example.com", 55 | "subject": "Refund Processed", 56 | "body": "Your refund of $500.00 has been processed." 57 | }, 58 | "depends_on": ["step2"] 59 | }, 60 | { 61 | "id": "step4", 62 | "tool": "db.write", 63 | "parameters": { 64 | "query": "UPDATE refund_requests SET status = ? WHERE id = ?", 65 | "params": ["COMPLETED", "REQ-456"] 66 | }, 67 | "depends_on": ["step2"] 68 | } 69 | ], 70 | "context": { 71 | "customer_id": "CUST-789", 72 | "request_id": "REQ-456", 73 | "refund_amount": 500.00 74 | } 75 | } 76 | ``` 77 | 78 | ## Validation Policy 79 | 80 | Here's a YAML policy specifically designed for financial transactions: 81 | 82 | ```yaml 83 | # finance_policy.yaml 84 | allow_tools: 85 | - db.query_ro 86 | - db.write 87 | - payments.transfer.small 88 | - notify.email 89 | - audit.log 90 | 91 | bounds: 92 | payments.transfer.small.amount: [0.01, 1000.00] 93 | 94 | deny_tokens_regex: 95 | - "DROP TABLE" 96 | - "1=1" 97 | - "password" 98 | - "secret" 99 | - "apikey" 100 | 101 | tool_patterns: 102 | payments.transfer.small: 103 | pattern: "payments.transfer" 104 | conditions: 105 | - "parameters.amount <= 1000.0" 106 | 107 | risk_weights: 108 | sql_injection: 0.8 109 | sensitive_data_exposure: 0.7 110 | excessive_amount: 0.6 111 | unauthorized_tool: 0.9 112 | 113 | fail_risk_threshold: 0.5 114 | max_steps: 10 115 | ``` 116 | 117 | ## Running Validation 118 | 119 | To validate the finance plan against this policy: 120 | 121 | ```bash 122 | plan-lint validate --plan finance_plan.json --policy finance_policy.yaml 123 | ``` 124 | 125 | ## Handling Violations 126 | 127 | Here are some common violations and how to address them: 128 | 129 | ### Excessive Transaction Amount 130 | 131 | ``` 132 | Violation: Parameter 'amount' value 5000.00 is outside bounds [0.01, 1000.00] 133 | ``` 134 | 135 | **Solution**: Break large transactions into smaller amounts or require additional authorization steps. 136 | 137 | ### SQL Injection Risk 138 | 139 | ``` 140 | Violation: Potential SQL injection detected in query 141 | ``` 142 | 143 | **Solution**: Always use parameterized queries with placeholders. 144 | 145 | ### Missing Audit Trail 146 | 147 | ``` 148 | Violation: Financial transaction missing corresponding audit logging step 149 | ``` 150 | 151 | **Solution**: Add an audit.log step after each financial transaction: 152 | 153 | ```json 154 | { 155 | "id": "audit_step", 156 | "tool": "audit.log", 157 | "parameters": { 158 | "event": "REFUND_PROCESSED", 159 | "details": { 160 | "amount": 500.00, 161 | "accounts": { 162 | "from": "COMPANY-MAIN", 163 | "to": "ACC-123" 164 | } 165 | } 166 | }, 167 | "depends_on": ["step2"] 168 | } 169 | ``` 170 | 171 | ## Integration with Agent System 172 | 173 | In a production environment, the Plan Validator would be integrated directly into the agent workflow: 174 | 175 | ```python 176 | from plan_lint import validate_plan 177 | from plan_lint.loader import load_policy 178 | 179 | # Load the finance policy 180 | finance_policy, rego_policy = load_policy("finance_policy.yaml") 181 | 182 | def validate_finance_plan(plan, context=None): 183 | """ 184 | Validate a financial transaction plan before execution. 185 | 186 | Args: 187 | plan: The plan to validate 188 | context: Optional context information 189 | 190 | Returns: 191 | (is_valid, violations): Tuple of validation result and any violations 192 | """ 193 | # Add additional context for validation 194 | if context is None: 195 | context = {} 196 | 197 | context["environment"] = "production" 198 | context["transaction_limits"] = { 199 | "standard": 1000.00, 200 | "premium": 5000.00 201 | } 202 | 203 | # Validate the plan 204 | result = validate_plan(plan, finance_policy, context=context) 205 | 206 | return result.valid, result.errors 207 | ``` 208 | 209 | By integrating Plan-Lint into your financial agent system, you can ensure that all plans are validated against security policies before execution, reducing the risk of financial fraud, data breaches, and operational errors. 210 | -------------------------------------------------------------------------------- /docs/examples/index.md: -------------------------------------------------------------------------------- 1 | # Examples Overview 2 | 3 | This section provides practical examples of using Plan-Lint to validate AI agent plans. 4 | 5 | ## Available Examples 6 | 7 | Plan-Lint can be used in various scenarios to validate AI agent plans. We provide several examples to demonstrate its capabilities: 8 | 9 | ### [Finance Agent System](finance-agent-system.md) 10 | 11 | This example demonstrates how to use Plan-Lint to validate financial transaction plans, including: 12 | 13 | - Detecting excessive transaction amounts 14 | - Validating proper account access 15 | - Ensuring proper audit logging 16 | - Preventing sensitive data exposure 17 | 18 | ### [SQL Injection Prevention](sql-injection.md) 19 | 20 | Learn how Plan-Lint detects and prevents SQL injection vulnerabilities in database queries: 21 | 22 | - Identifying vulnerable query patterns 23 | - Using parameterized queries 24 | - Creating custom SQL validation rules 25 | - Integrating with data access layers 26 | 27 | ### [Custom Rules](custom-rules.md) 28 | 29 | Discover how to extend Plan-Lint with custom validation rules for your specific needs: 30 | 31 | - Creating Python validation functions 32 | - Developing Rego policies 33 | - Defining YAML rule patterns 34 | - Integrating custom rules with CI/CD pipelines 35 | 36 | ## Using the Examples 37 | 38 | Each example provides: 39 | 40 | 1. **Problem Description**: What security or operational issue is being addressed 41 | 2. **Vulnerable Plan**: An example of a problematic plan 42 | 3. **Validation Policy**: The Plan-Lint policy to detect the issue 43 | 4. **Fixed Plan**: A corrected version that addresses the vulnerability 44 | 5. **Integration Code**: How to integrate the validation into your systems 45 | 46 | You can use these examples as templates for your own implementations or as learning resources to understand common validation patterns. 47 | 48 | ## Running the Examples 49 | 50 | To run any of the examples, make sure you have Plan-Lint installed: 51 | 52 | ```bash 53 | pip install plan-lint 54 | ``` 55 | 56 | Then, follow the specific instructions in each example page. Typically, you'll: 57 | 58 | 1. Save the example plan to a JSON file 59 | 2. Save the policy to a YAML or Rego file 60 | 3. Run the validation command 61 | 4. Examine the results 62 | 63 | For example: 64 | 65 | ```bash 66 | plan-lint validate --plan example_plan.json --policy example_policy.yaml 67 | ``` 68 | 69 | We encourage you to modify the examples to fit your specific use cases and experiment with different validation rules. 70 | -------------------------------------------------------------------------------- /docs/examples/sql-injection.md: -------------------------------------------------------------------------------- 1 | # SQL Injection Prevention 2 | 3 | This example shows how Plan-Lint can detect and prevent SQL injection vulnerabilities. 4 | 5 | ## Understanding SQL Injection 6 | 7 | SQL injection is a code injection technique that exploits vulnerabilities in applications that interact with databases. Attackers can insert malicious SQL code that can: 8 | 9 | - Bypass authentication 10 | - Access sensitive data 11 | - Modify database content 12 | - Delete database data 13 | - Execute administrative operations 14 | 15 | ## Vulnerable Plan Example 16 | 17 | Consider a plan with a potential SQL injection vulnerability: 18 | 19 | ```json 20 | { 21 | "goal": "Retrieve user information", 22 | "steps": [ 23 | { 24 | "id": "step1", 25 | "tool": "db.query", 26 | "parameters": { 27 | "query": "SELECT * FROM users WHERE username = '" + user_input + "'" 28 | } 29 | }, 30 | { 31 | "id": "step2", 32 | "tool": "notify.email", 33 | "parameters": { 34 | "to": "admin@example.com", 35 | "subject": "User Query Results", 36 | "body": "Query results: {{step1.result}}" 37 | }, 38 | "depends_on": ["step1"] 39 | } 40 | ] 41 | } 42 | ``` 43 | 44 | In this example, the user input is directly concatenated into the SQL query, creating a vulnerability. If a malicious user provides input like `admin' OR '1'='1`, the query becomes: 45 | 46 | ```sql 47 | SELECT * FROM users WHERE username = 'admin' OR '1'='1' 48 | ``` 49 | 50 | This would return all users in the database, potentially exposing sensitive information. 51 | 52 | ## Detection with Plan-Lint 53 | 54 | Plan-Lint can detect potential SQL injection vulnerabilities in plans. To validate the plan: 55 | 56 | ```bash 57 | plan-lint validate --plan vulnerable_query_plan.json 58 | ``` 59 | 60 | Plan-Lint would produce output similar to: 61 | 62 | ``` 63 | Validation Results: 64 | ✘ Plan validation failed with 1 violation 65 | 66 | Violations: 67 | - [HIGH] sql_injection: Potential SQL injection detected in query (step: step1) 68 | SQL query contains string concatenation patterns which is a common indicator of SQL injection vulnerability 69 | ``` 70 | 71 | ## SQL Injection Policy 72 | 73 | A policy to detect SQL injection might look like this: 74 | 75 | ```yaml 76 | # sql_security_policy.yaml 77 | allow_tools: 78 | - db.query 79 | - db.query_ro 80 | - notify.email 81 | 82 | deny_tokens_regex: 83 | - "'.*--" 84 | - "1=1" 85 | - "'; DROP" 86 | - "'.*OR.*'.*=.*'" 87 | - "'.*AND.*'.*=.*'" 88 | 89 | risk_weights: 90 | sql_injection: 0.9 91 | 92 | fail_risk_threshold: 0.3 93 | ``` 94 | 95 | ## Fixed Plan Example 96 | 97 | A safer version of the plan would use parameterized queries: 98 | 99 | ```json 100 | { 101 | "goal": "Retrieve user information", 102 | "steps": [ 103 | { 104 | "id": "step1", 105 | "tool": "db.query", 106 | "parameters": { 107 | "query": "SELECT * FROM users WHERE username = ?", 108 | "params": [user_input] 109 | } 110 | }, 111 | { 112 | "id": "step2", 113 | "tool": "notify.email", 114 | "parameters": { 115 | "to": "admin@example.com", 116 | "subject": "User Query Results", 117 | "body": "Query results: {{step1.result}}" 118 | }, 119 | "depends_on": ["step1"] 120 | } 121 | ] 122 | } 123 | ``` 124 | 125 | In this fixed example: 126 | 127 | 1. User input is provided as a parameter rather than being concatenated into the query 128 | 2. The database driver handles proper escaping of the input 129 | 3. The query structure remains constant regardless of input values 130 | 131 | ## Advanced SQL Injection Prevention 132 | 133 | ### Using Prepared Statements 134 | 135 | For more complex queries, use prepared statements with named parameters: 136 | 137 | ```json 138 | { 139 | "id": "step1", 140 | "tool": "db.query", 141 | "parameters": { 142 | "query": "SELECT * FROM users WHERE username = :username AND status = :status", 143 | "params": { 144 | "username": user_input, 145 | "status": "active" 146 | } 147 | } 148 | } 149 | ``` 150 | 151 | ### Custom Validation Rules 152 | 153 | You can create custom SQL validation rules for specific database systems: 154 | 155 | ```python 156 | from typing import List 157 | from plan_lint.types import Plan, PlanError, ErrorCode 158 | 159 | def check_sql_patterns(plan: Plan) -> List[PlanError]: 160 | """Check for problematic SQL patterns specific to your database.""" 161 | errors = [] 162 | 163 | for i, step in enumerate(plan.steps): 164 | if step.tool.startswith("db."): 165 | query = step.parameters.get("query", "") 166 | 167 | # Check for database-specific issues 168 | if "INFORMATION_SCHEMA" in query: 169 | errors.append( 170 | PlanError( 171 | step=i, 172 | code=ErrorCode.CUSTOM, 173 | msg="Query attempts to access system tables" 174 | ) 175 | ) 176 | 177 | # Check for unparameterized LIKE queries 178 | if "LIKE '%" in query: 179 | errors.append( 180 | PlanError( 181 | step=i, 182 | code=ErrorCode.CUSTOM, 183 | msg="LIKE statements should use parameters for pattern values" 184 | ) 185 | ) 186 | 187 | return errors 188 | ``` 189 | 190 | ## Integration with Data Access Layer 191 | 192 | For production systems, consider implementing a data access layer that enforces parameterized queries: 193 | 194 | ```python 195 | from plan_lint import validate_plan 196 | from plan_lint.types import Plan, PlanStep 197 | 198 | def create_db_query_step(query: str, params: list) -> PlanStep: 199 | """ 200 | Create a safe database query step that enforces parameterization. 201 | 202 | Args: 203 | query: SQL query with parameter placeholders 204 | params: List of parameter values 205 | 206 | Returns: 207 | A safe PlanStep for database queries 208 | """ 209 | # Validate that the query uses parameters 210 | if "?" not in query and ":" not in query: 211 | raise ValueError("Query must use parameterized format") 212 | 213 | return PlanStep( 214 | id="db_query", 215 | tool="db.query_ro", 216 | parameters={ 217 | "query": query, 218 | "params": params 219 | } 220 | ) 221 | ``` 222 | 223 | By using Plan-Lint to validate database operations in your agent plans, you can significantly reduce the risk of SQL injection vulnerabilities and maintain a more secure system. 224 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Plan-Linter 2 | 3 | This guide will help you get up and running with Plan-Linter. 4 | 5 | ## Installation 6 | 7 | ### Using pip 8 | 9 | ```bash 10 | pip install plan-lint 11 | ``` 12 | 13 | ### From source 14 | 15 | ```bash 16 | git clone https://github.com/cirbuk/plan-lint.git 17 | cd plan-lint 18 | pip install -e . 19 | ``` 20 | 21 | ## Basic Usage 22 | 23 | The simplest way to use Plan-Linter is to run it on a JSON plan file: 24 | 25 | ```bash 26 | plan-lint path/to/plan.json 27 | ``` 28 | 29 | This will validate the plan against the default schema and report any issues. 30 | 31 | ## Using a Policy File 32 | 33 | For more control, create a policy YAML file: 34 | 35 | ```yaml 36 | # policy.yaml 37 | allow_tools: 38 | - sql.query_ro 39 | - priceAPI.calculate 40 | bounds: 41 | priceAPI.calculate.discount_pct: [-40, 0] 42 | deny_tokens_regex: 43 | - "AWS_SECRET" 44 | - "API_KEY" 45 | max_steps: 50 46 | risk_weights: 47 | tool_write: 0.4 48 | raw_secret: 0.5 49 | fail_risk_threshold: 0.8 50 | ``` 51 | 52 | Then run Plan-Linter with the policy: 53 | 54 | ```bash 55 | plan-lint path/to/plan.json --policy policy.yaml 56 | ``` 57 | 58 | ## Output Formats 59 | 60 | Plan-Linter can output in different formats: 61 | 62 | ### CLI (default) 63 | 64 | ```bash 65 | plan-lint path/to/plan.json 66 | ``` 67 | 68 | This shows a rich formatted report in the terminal. 69 | 70 | ### JSON 71 | 72 | ```bash 73 | plan-lint path/to/plan.json --format json 74 | ``` 75 | 76 | This outputs a machine-readable JSON report. 77 | 78 | ### Saving Output 79 | 80 | To save the output to a file: 81 | 82 | ```bash 83 | plan-lint path/to/plan.json --output results.txt 84 | ``` 85 | 86 | Or for JSON: 87 | 88 | ```bash 89 | plan-lint path/to/plan.json --format json --output results.json 90 | ``` 91 | 92 | ## CI Integration 93 | 94 | Plan-Linter can be integrated into CI pipelines. Add this to your GitHub workflow: 95 | 96 | ```yaml 97 | - name: Lint agent plan 98 | run: | 99 | plan-lint path/to/plan.json --policy policy.yaml 100 | ``` 101 | 102 | The command will return a non-zero exit code if the plan fails validation, which will fail the CI step. 103 | 104 | ## Next Steps 105 | 106 | - See the [README](../README.md) for more examples 107 | - Read the [Implementation Details](../IMPLEMENTATION.md) 108 | - Check out the [Contributing Guide](../CONTRIBUTING.md) -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Plan-Lint SDK 3 | --- 4 | 5 | # Plan-Lint SDK 6 | 7 |