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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
*.py text eol=lf

panoptic/data/*.csv whitespace=cr-at-eol
27 changes: 27 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash
# Pre-commit hook for Panoptic
#
# This hook delegates to the pre-commit framework for consistent tool configuration.
# The canonical check definitions are in .pre-commit-config.yaml.
#
# Setup options:
# Option A (recommended): Use pre-commit framework directly
# pip install pre-commit && pre-commit install
#
# Option B: Use this script via git config
# git config core.hooksPath .githooks
#
# Option C: Copy to .git/hooks
# cp .githooks/pre-commit .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit

# All setup options require the pre-commit package. Keeping one execution path
# ensures the pinned hooks and arguments in .pre-commit-config.yaml are always
# used, including Ruff fixes, formatting, strict mypy, Markdown linting, and the
# project dependency audit.
if ! command -v pre-commit >/dev/null 2>&1; then
echo "Error: pre-commit is required to run the Panoptic checks." >&2
echo "Install it with: python -m pip install pre-commit" >&2
exit 1
fi

exec pre-commit run
7 changes: 7 additions & 0 deletions .github/constraints-min.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Lower bounds of the declared runtime dependencies (see pyproject.toml).
# Used by the "minimum-versions" CI job to prove Panoptic works against the
# oldest dependency releases it claims to support, not just the newest.
httpx==0.28.1
rich==15.0.0
rich-argparse==1.4
tomli==2.0; python_version < "3.11"
24 changes: 16 additions & 8 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
- package-ecosystem: pip
directory: /
schedule:
interval: "weekly"
interval: weekly
commit-message:
prefix: "deps"
- package-ecosystem: "github-actions"
directory: "/"
prefix: deps
groups:
python-dependencies:
patterns:
- "*"
- package-ecosystem: github-actions
directory: /
schedule:
interval: "weekly"
interval: weekly
commit-message:
prefix: "ci"
prefix: ci
groups:
github-actions:
patterns:
- "*"
68 changes: 68 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
tests:
name: Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
cache: pip
- run: python -m pip install --upgrade pip
- run: python -m pip install -e ".[dev]"
- run: pytest -q

minimum-versions:
name: Minimum dependency versions (Python 3.10)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.10"
cache: pip
- run: python -m pip install --upgrade pip
# Install the package pinned to the lowest declared runtime dependency
# versions. Install only the test dependencies separately so unrelated
# development tools cannot raise those runtime lower bounds.
- run: python -m pip install -e . -c .github/constraints-min.txt
- run: python -m pip install pytest pytest-asyncio pytest-httpx -c .github/constraints-min.txt
- run: python -m pip check
- run: pytest -q

quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
cache: pip
- run: python -m pip install --upgrade pip
- run: python -m pip install -e ".[dev]"
- run: ruff check .
- run: ruff format --check .
- run: mypy panoptic
- run: python -m build
- run: python -m twine check dist/*
- run: pip-audit .
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ output/
.worktrees/
.venv/
panoptic.egg-info/
build/
dist/
.coverage
htmlcov/
.mcp.json
CLAUDE.md
.claude/
.claire/
.codex-review/
.coderabbit-results/
docs/
.fathom/
docs/
14 changes: 7 additions & 7 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.6
rev: v0.16.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.16.0
rev: v2.3.0
hooks:
- id: mypy
exclude: ^(panoptic\.py|tests/|\.claire/)
Expand All @@ -25,10 +25,10 @@ repos:
exclude: ^(docs/superpowers/|\.claire/)

- repo: https://github.com/pypa/pip-audit
rev: v2.10.0
rev: v2.10.1
hooks:
- id: pip-audit
# Audit the project's declared dependencies (pyproject.toml), not the
# ambient hook environment — the latter reports CVEs in pip-audit's own
# transitive tooling, which are irrelevant to Panoptic.
args: [".", "--desc"]
# Audit the project's declared dependencies rather than pip-audit's
# ambient hook environment.
args: [--desc=on, .]
pass_filenames: false
163 changes: 159 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@ path traversal vulnerabilities.
* TOML config files for persistent settings (`--config`)
* Multiple traversal bypass techniques: prefixes, postfixes,
multiplier, slash replacement, double encoding
* HTTP/HTTPS and SOCKS4/SOCKS5 proxy support with validation
* HTTP/HTTPS and SOCKS5 proxy support with validation
* Random or custom User-Agent, cookie, and header support
* Credential redaction in banner and log output
* Credential redaction in banner, log, and machine-readable output
(including numeric and boolean JSON body values)
* Sensitive artifacts hardened with owner-only `0600` permissions on
POSIX and final-component symlink protection where the OS supports it
* Self-update with remote URL verification (`--update`)

## Requirements
Expand Down Expand Up @@ -197,28 +200,180 @@ panoptic --url "http://target/include.php?file=test.txt" \
--config ~/.config/panoptic/config.toml
```

Default config location: `~/.config/panoptic/config.toml`
Default config location: `~/.config/panoptic/config.toml` (loaded
automatically when present, even without `--config`).

```toml
[defaults]
# Any long option name (with dashes as underscores) is accepted here,
# including the target and output destinations.
url = "http://target/include.php?file=test.txt"
concurrency = 8
verbose = true
automatic = true
all_versions = true
output_format = "json"
output_file = "results.json"
log_file = "scan.log"
resume_file = "scan.checkpoint"

[proxy]
url = "socks5://127.0.0.1:9050"

[headers]
user_agent = "Mozilla/5.0"
cookie = "sid=foobar; auth=1"
values = ["X-Forwarded-For: 127.0.0.1"]
```

Priority: CLI args > config file > built-in defaults.
Priority: **CLI args > config file > built-in defaults.**

### Config-supported target and output options

The `[defaults]` table accepts any scan option, not just performance
tuning. In particular you can persist:

* `url` — the default target (override per-run with `--url`)
* `output_format`, `output_file` — where machine-readable results go
* `log_file` — mirror console output to a file
* `resume_file` — checkpoint location for resumable scans

Sensitive artifacts written by Panoptic — the log file, the results/list
output file, and any files saved with `--write-files` — are forced to
owner-only `0600` permissions on POSIX. Platforms exposing `O_NOFOLLOW`
also atomically refuse a pre-existing symlink at the final path component.
On platforms without `O_NOFOLLOW`, Panoptic performs a best-effort
pre-open symlink/junction check, but the check cannot eliminate a race.
Windows mode bits do not configure NTFS ACLs, so use an appropriately
restricted directory when artifacts may contain sensitive data.

### Overriding config booleans on the command line

Every boolean flag has a `--no-` counterpart, so a value set to `true`
in the config file can be turned off for a single run without editing
the file:

```bash
# config.toml sets verbose = true and automatic = true
panoptic --url "http://target/x.php?file=test.txt" --no-verbose --no-auto
```

Because an omitted boolean flag is left unset (rather than defaulting to
`false`), the config value is used unless you pass the flag or its
`--no-` form explicitly.

## Proxying

Route traffic through an HTTP(S) or SOCKS proxy:

```bash
panoptic --url "https://target/x.php?file=test.txt" \
--proxy "socks5://127.0.0.1:9050"
```

* Accepted schemes: `http://`, `https://`, `socks5://`, `socks5h://`
(`socks5h` resolves DNS through the proxy — useful for Tor and to
avoid local DNS leaks). SOCKS4 is not supported by the underlying
HTTP client. The scheme and host are validated before the scan starts.
* SOCKS support comes from the `httpx[socks]` extra, which is installed
by default.
* By default Panoptic honours the standard `HTTP_PROXY` / `HTTPS_PROXY` /
`NO_PROXY` environment variables. Pass `--ignore-proxy` to bypass them
and connect directly (this also disables any env-configured proxy).
* Combine with `--invalid-ssl` when intercepting HTTPS through a proxy
with its own CA. This disables certificate verification and is unsafe
on untrusted networks.

## Version Expansion (`--all-versions`)

Some bundled paths are version templates (for example JBoss release
directories written as `[JBOSS]`). By default these template rows are
skipped, because a literal `[JBOSS]` path can never match a real file.
Passing `--all-versions` expands each template against the bundled
version list, adding one concrete path per known version — a much larger
but far more thorough scan:

```bash
panoptic --url "http://target/x.php?file=test.txt" --auto --all-versions
```

## Response Comparison

Panoptic does not classify a path from the target-controlled
`Content-Length` header alone. It compares the complete normalized body
against the invalid-path baseline. A cheap similarity upper bound avoids
the full comparison only when it can already prove the bodies differ;
ambiguous responses use the exact ratio to avoid reordered-body false
negatives.

## Resume / Checkpoints

`--resume-file PATH` records completed cases so an interrupted scan can
continue where it left off:

```bash
panoptic --url "http://target/x.php?file=test.txt" \
--resume-file scan.checkpoint
# ... interrupt with Ctrl-C, then re-run the same command to resume
```

The checkpoint stores only completed case IDs plus a SHA-256
**fingerprint** of the scan definition (URL, injection parameters,
detection options, headers, cookie, User-Agent, and the exact case set).
It never stores credentials, headers, or URLs in plaintext.

On resume, the fingerprint is recomputed and compared. If anything that
would change the meaning of the scan differs — a different target,
parameter, header, cookie, filter set, or `--all-versions` toggle — the
checkpoint is rejected with a warning and the scan starts fresh, so a
stale checkpoint can never silently skip cases from a different scan.
Legacy bare-list checkpoints are accepted with a warning and restricted
to case IDs present in the current scan; because that old format has no
fingerprint, users should replace it with the newly written format.
Failed (network-error) requests are deliberately **not** checkpointed so
they are retried on resume.

## Version and Update

```bash
panoptic --version # print the installed version and exit
panoptic --update # fast-forward update from the official GitHub repo
```

`--update` only works from a git checkout whose `origin` remote uses
HTTPS or SSH and matches the official upstream (verified before pulling,
to resist insecure or tampered remote configuration). It fast-forwards
the checked-out `main` branch from the explicit upstream `main` ref; for
pip installs it prints the correct
`pip install -U` command instead. When run from a checkout, the banner
also shows the current short git revision.

## Exit Codes

Panoptic uses conventional process exit codes so it can be scripted:

* `0` — Success: the scan (or `--list` / `--update`) completed. Individual
requests that exhaust their retries are warned about and remain eligible
for a resumed scan, but do not fail an otherwise successful run.
* `1` — Invalid usage: missing or invalid arguments, or no injectable
parameter could be found.
* `2` — Operational failure: cannot connect, no matching test cases, all
queued requests failed, or a worker, checkpoint, output write, or
configuration failed.
* `130` — Interrupted by the user (Ctrl-C / SIGINT).

## Contributing

Contributions are welcome! Please open issues or pull requests on
[GitHub](https://github.com/lightos/Panoptic).

## Testbed

For safe, reproducible end-to-end testing, see the
[Panoptic LFI Testbed](https://github.com/lightos/panoptic-lfi-testbed).
It provides deliberately vulnerable endpoints covering Panoptic's supported
injection methods and traversal transformations.

## License

This project is licensed under the MIT License - see the
Expand Down
2 changes: 1 addition & 1 deletion panoptic/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def main() -> None:
from panoptic.cli import run

try:
asyncio.run(run())
sys.exit(asyncio.run(run()))
except KeyboardInterrupt:
sys.exit(130)

Expand Down
Loading