Skip to content

Repository files navigation

ApiPosture.php

Build and Test Packagist Version Packagist Downloads License: MIT PHP Ko-fi

A CLI security inspection tool for PHP APIs. Performs static source-code analysis using nikic/php-parser to identify authorization misconfigurations and security risks in Laravel, Symfony, and Slim applications.

Terminal Output

Terminal Output

Findings Report

Findings Report

Features

  • Static analysis of PHP projects (no runtime required)
  • Discovers endpoints from Laravel routes, Symfony attributes, and Slim route definitions
  • Detects 8 common security issues with authorization
  • Multiple output formats: Terminal, JSON, Markdown, HTML
  • Sorting, filtering, and grouping of results
  • Configuration file support with suppressions
  • Accessibility options (no-color, no-icons)
  • CI/CD integration with --fail-on exit codes
  • Works with PHP 8.1+

Installation

# Install globally (recommended — scan any project from anywhere)
composer global require apiposture/apiposture

# Or install as a dev dependency inside your project
composer require --dev apiposture/apiposture

Global install tip: After composer global require, make sure Composer's global bin directory is on your PATH. The location varies by system — find yours with composer global config bin-dir --absolute, then add it:

# Modern Linux / macOS (Composer 2.x)
export PATH="$PATH:$HOME/.config/composer/vendor/bin"

# Older Linux / macOS
export PATH="$PATH:$HOME/.composer/vendor/bin"

# Add whichever applies to ~/.bashrc or ~/.zshrc to make it permanent

Then run apiposture scan /path/to/project from anywhere.

Project install tip: vendor/bin/apiposture must be run from the directory where you ran composer require (where the vendor/ folder lives). Pass the target path as an argument:

# Installed in ~/myapp, scanning a subdirectory
cd ~/myapp
vendor/bin/apiposture scan src/Controller

# Scanning a completely separate project
vendor/bin/apiposture scan /path/to/other-project

Usage

# Scan a project directory (global install)
apiposture scan /path/to/project

# Scan a project directory (project install — run from vendor root)
vendor/bin/apiposture scan /path/to/project

# Scan a subdirectory
vendor/bin/apiposture scan ./src/Controller

# Output as JSON
vendor/bin/apiposture scan . --output json

# Output as Markdown report
vendor/bin/apiposture scan . --output markdown --output-file report.md

# Output as HTML report
vendor/bin/apiposture scan . --output html --output-file report.html

# Filter by severity
vendor/bin/apiposture scan . --severity medium

# CI integration - fail if high severity findings
vendor/bin/apiposture scan . --fail-on high

# Sorting
vendor/bin/apiposture scan . --sort-by route --sort-dir asc

# Filtering
vendor/bin/apiposture scan . --classification public --method POST
vendor/bin/apiposture scan . --route-contains admin --controller UserController

# Grouping
vendor/bin/apiposture scan . --group-by controller
vendor/bin/apiposture scan . --group-findings-by severity

# Accessibility (no colors/icons)
vendor/bin/apiposture scan . --no-color --no-icons

# Use config file
vendor/bin/apiposture scan . --config .apiposture.json

Supported Frameworks

Laravel

  • Route::get(), Route::post(), Route::put(), Route::delete(), Route::patch()
  • Route::middleware(['auth'])->group(...) with nested routes
  • Route::prefix('/api')->group(...) with path prefixes
  • Controller middleware via $this->middleware('auth') in constructors
  • #[Middleware('auth')] attributes (Laravel 11+)
  • Auth detection: auth, auth:sanctum, auth:api, role:*, can:*, guest

Symfony

  • #[Route('/path', methods: ['GET'])] attributes
  • #[IsGranted('ROLE_ADMIN')] security attributes
  • #[Security("is_granted('ROLE_USER')")] expressions
  • Class-level route prefixes and security inheritance
  • ROLE_* pattern detection

Slim

  • $app->get(), $app->post(), $app->put(), $app->delete(), $app->patch()
  • $app->group('/prefix', ...) with nested routes
  • ->add(new AuthMiddleware()) middleware chains
  • Auth middleware detection via naming conventions

Configuration File

Create .apiposture.json in your project root:

{
  "severity": { "default": "low", "failOn": "high" },
  "suppressions": [
    { "route": "/api/health", "ruleId": "AP001" },
    { "route": "/api/webhook/*", "ruleId": "AP002" }
  ],
  "rules": {
    "AP006": { "enabled": false }
  },
  "display": { "useColors": true, "useIcons": true }
}

Security Rules

Rule ID Name Severity Description
AP001 Public without explicit intent Medium Endpoint is publicly accessible without guest middleware or explicit marker
AP002 AllowAnonymous on write High* Public POST/PUT/DELETE/PATCH operations
AP003 Controller/action conflict High Action overrides controller-level auth with anonymous access
AP004 Missing auth on writes Critical Write endpoint with zero authentication whatsoever
AP005 Excessive role access Medium More than 3 roles on a single endpoint
AP006 Weak role naming Low Generic role names like "user", "admin", "guest"
AP007 Sensitive route keywords High/Low admin, debug, export, secret in routes (High without auth, Low with auth)
AP008 Unprotected endpoint High/Info Endpoint with no auth middleware (High for writes, Info for reads)

*AP002 severity adjusts dynamically: webhooks → Medium, auth/login endpoints → Low, analytics → Low

Example Output

Sample Terminal Output

ApiPosture Scan Results
────────────────────────────────────────────────────────
  Path:     /path/to/project
  Duration: 0.25s
  Files:    15 scanned

Summary
+-------------+-------+
| Metric      | Count |
+-------------+-------+
| Endpoints   | 42    |
| Findings    | 8     |
|   Critical  | 1     |
|   High      | 3     |
|   Medium    | 2     |
|   Low       | 2     |
+-------------+-------+

Endpoints
+---------------------+---------+----------------+----------------+------+
| Route               | Methods | Classification | Controller     | Auth |
+---------------------+---------+----------------+----------------+------+
| /api/users          | GET     | Authenticated  | UserController | Yes  |
| /api/admin          | GET     | Public         | AdminController| No   |
| /api/orders         | POST    | Role Restricted| OrderController| Yes  |
+---------------------+---------+----------------+----------------+------+

GitHub Actions Integration

Create .github/workflows/api-security-scan.yml:

Basic Workflow

name: API Security Scan

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  security-scan:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: '8.3'

    - name: Install dependencies
      run: composer install --prefer-dist --no-progress

    - name: Scan API for security issues
      run: vendor/bin/apiposture scan ./app --fail-on high

Advanced Workflow with Reports

name: API Security Scan

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  security-scan:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: '8.3'

    - name: Install dependencies
      run: composer install --prefer-dist --no-progress

    - name: Run security scan
      id: scan
      continue-on-error: true
      run: |
        vendor/bin/apiposture scan ./app \
          --output json \
          --output-file scan-results.json \
          --fail-on high

    - name: Generate Markdown report
      if: always()
      run: |
        vendor/bin/apiposture scan ./app \
          --output markdown \
          --output-file api-security-report.md

    - name: Upload JSON results
      if: always()
      uses: actions/upload-artifact@v4
      with:
        name: security-scan-json
        path: scan-results.json

    - name: Upload Markdown report
      if: always()
      uses: actions/upload-artifact@v4
      with:
        name: security-scan-report
        path: api-security-report.md

    - name: Comment PR with results
      if: github.event_name == 'pull_request' && always()
      uses: actions/github-script@v7
      with:
        script: |
          const fs = require('fs');
          const report = fs.readFileSync('api-security-report.md', 'utf8');
          github.rest.issues.createComment({
            issue_number: context.issue.number,
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: `## API Security Scan Results\n\n${report}`
          });

    - name: Fail if high severity issues found
      if: steps.scan.outcome == 'failure'
      run: exit 1

Configuration Options

  • --fail-on <severity>: Exit with code 1 if findings of specified severity or higher are found
  • --output json: Generate machine-readable JSON output for further processing
  • --output markdown: Generate human-readable Markdown reports
  • --output html: Generate self-contained HTML reports
  • --severity <level>: Set minimum severity level to report
  • --config .apiposture.json: Use configuration file for suppressions and custom rules

Exit Codes

  • 0: Scan completed successfully with no findings above the fail threshold
  • 1: Findings above the fail threshold were detected, or error during scan

Project Structure

ApiPosture.php/
├── src/
│   ├── Core/
│   │   ├── Analyzer/          # Scan orchestrator
│   │   ├── Classification/    # Security classifier
│   │   ├── Config/            # Configuration loader
│   │   ├── Discovery/         # Laravel, Symfony, Slim discoverers
│   │   └── Model/             # Endpoint, Finding, ScanResult, Enums
│   ├── Rules/                 # 8 security rules + engine
│   ├── Output/                # Terminal, JSON, Markdown formatters
│   └── Command/               # CLI scan command
├── tests/
│   ├── Core/
│   ├── Rules/
│   ├── Output/
│   ├── Command/
│   └── Fixtures/              # Laravel, Symfony, Slim sample code
├── bin/apiposture              # CLI entry point
└── composer.json

Building from Source

# Clone and install
git clone https://github.com/BlagoCuljak/ApiPosture.php.git
cd ApiPosture.php
composer install

# Run tests
vendor/bin/phpunit

# Run against sample fixtures
vendor/bin/apiposture scan tests/Fixtures/Laravel
vendor/bin/apiposture scan tests/Fixtures/Symfony --output json
vendor/bin/apiposture scan tests/Fixtures/Slim --output markdown

Related Projects

Contributing

We welcome contributions! Please:

License

MIT

About

Know your PHP project security API posture

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages