Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
3d37a92
feat(api): add ATTACK_PATHS_SINK_DATABASE setting and Neptune databas…
josema-xyz Apr 23, 2026
b9d413e
feat(api): add staging Neo4j driver for per-scan cartography databases
josema-xyz Apr 23, 2026
a3c3077
feat(api): add Neo4j sink implementation and SinkDatabase protocol
josema-xyz Apr 23, 2026
dc764f2
feat(api): add Neptune sink with dual Bolt drivers and SigV4 auth
josema-xyz Apr 23, 2026
3ffd9df
refactor(api): rewrite attack-paths database module as shim over stag…
josema-xyz Apr 23, 2026
34246e0
feat(api): init attack-paths drivers post-fork on Celery workers
josema-xyz Apr 23, 2026
eb444eb
fix(api): re-init attack-paths drivers post-fork on gunicorn workers
josema-xyz Apr 23, 2026
8db11f3
feat(api): add is_neptune flag to AttackPathsScan to route reads per-…
josema-xyz Apr 23, 2026
7107329
feat(api): stamp is_neptune on AttackPathsScan at creation time
josema-xyz Apr 23, 2026
9744f8b
feat(api): scope sync MATCH by provider label and skip CREATE INDEX o…
josema-xyz Apr 23, 2026
7892424
feat(api): add legacy Neo4j drain, ops helper, and list_neo4j_tenant_…
josema-xyz Apr 23, 2026
9af3974
feat(api): drain legacy Neo4j tenant data after Neptune scans succeed
josema-xyz Apr 23, 2026
0448561
feat(api): route attack-paths reads by scan.is_neptune and add Neptun…
josema-xyz Apr 23, 2026
37f7beb
feat(api): recover graph_data_ready against the scan row's recorded sink
josema-xyz Apr 23, 2026
00300ab
docs(api): add changelog entry for Neptune sink and gunicorn fork-safety
josema-xyz Apr 23, 2026
6e2fc5f
test(api): add sink factory, legacy drain, and relationship template …
josema-xyz Apr 23, 2026
ed0c693
Merge branch 'master' of github.com:prowler-cloud/prowler into PROWLE…
josema-xyz May 4, 2026
85d99f5
refactor(api): tighten attack-paths Neptune cutover plumbing
josema-xyz May 4, 2026
2f35a83
docs(api): use single backticks in attack-paths docstrings
josema-xyz May 4, 2026
13520f3
style(api): replace em dashes in cutover docstrings
josema-xyz May 4, 2026
c0af7b4
Merge branch 'master' of github.com:prowler-cloud/prowler into PROWLE…
josema-xyz May 11, 2026
a88e546
test(api): align attack-paths tests with the sink package refactor
josema-xyz May 11, 2026
c22e977
refactor(api): drop defensive getattr fallbacks for guaranteed attack…
josema-xyz May 11, 2026
0893f67
fix(attack-paths): update neptune connection parameters
josema-xyz May 12, 2026
b17ff83
fix(api): per-call sessions in attack-paths sync and Neptune index skip
josema-xyz May 12, 2026
2076f04
fix(attack-paths): fix aws queries to full neptune compatibility
josema-xyz May 12, 2026
39f7507
refactor(api): move attack-paths sync write path into each sink
josema-xyz May 13, 2026
d451fd5
refactor(api): rewrite attack-paths queries to read list properties v…
josema-xyz May 13, 2026
992556c
docs(attack-paths): document list-typed property read patterns
josema-xyz May 13, 2026
ffd7e04
chore(skill): update attack-paths-query for Neptune-compatible list p…
josema-xyz May 13, 2026
dcda062
docs(readme): document Neptune sink configuration for Attack Paths
josema-xyz May 13, 2026
023d871
perf(api): push effect=Allow filter into AWSPolicyStatement pattern a…
josema-xyz May 13, 2026
46f358a
refactor(api): make attack-paths property coercion a sink-agnostic co…
josema-xyz May 13, 2026
1118a8b
test(api): cover sink-choice gaps and refresh stale cartography patch…
josema-xyz May 13, 2026
bdcebba
Merge branch 'master' of github.com:prowler-cloud/prowler into PROWLE…
josema-xyz May 19, 2026
c89f7f5
refactor(api): update health checks for Neo4j and Neptune attack path…
josema-xyz May 19, 2026
5c1f1b1
perf(attack-paths): speed up aws-iam-statements-allow-all-actions on …
josema-xyz May 19, 2026
cb28465
perf(attack-paths): rewrite aws.py queries for Neptune and align docs…
josema-xyz May 19, 2026
ee897b9
chore(attack-paths): bump cartography to 0.136.0
josema-xyz May 26, 2026
ff7e60d
Merge branch 'master' of github.com:prowler-cloud/prowler into PROWLE…
josema-xyz May 26, 2026
b464e81
feat(api): materialise list-typed cartography properties as child nod…
josema-xyz May 26, 2026
9441e68
Merge branch 'master' of github.com:prowler-cloud/prowler into PROWLE…
josema-xyz May 27, 2026
f5b4c34
perf(attack-paths): rewrite aws.py queries to multi-MATCH shape for N…
josema-xyz May 27, 2026
aca4121
Merge branch 'master' of github.com:prowler-cloud/prowler into PROWLE…
josema-xyz Jun 10, 2026
d37559a
fix(api): renumber attack_paths_scan is_migrated migration after merge
josema-xyz Jun 10, 2026
7be62ef
docs(changelog): add PR number to Neptune sink entry
josema-xyz Jun 10, 2026
eb801e6
docs(skill): add language hints to code fences in attack-paths-query …
josema-xyz Jun 10, 2026
7c7bb30
chore(api): revert uv.lock
josema-xyz Jun 10, 2026
60eca70
fix(api): index temp Neo4j DB on Neptune sink, sign SigV4 Host with p…
josema-xyz Jun 10, 2026
b3ce7c2
Merge branch 'master' of github.com:prowler-cloud/prowler into PROWLE…
josema-xyz Jun 12, 2026
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,7 @@ GEMINI.md

# Claude Code
.claude/*

# Docker
docker-compose.override.yml
docker-compose-dev.override.yml
35 changes: 27 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,35 @@ prowler dashboard

## Attack Paths

Attack Paths automatically extends every completed AWS scan with a Neo4j graph that combines Cartography's cloud inventory with Prowler findings. The feature runs in the API worker after each scan and therefore requires:
Attack Paths automatically extends every completed AWS scan with a graph that combines Cartography's cloud inventory with Prowler findings. The feature runs in the API worker after each scan.

- An accessible Neo4j instance (the Docker Compose files already ships a `neo4j` service).
- The following environment variables so Django and Celery can connect:
Two graph backends are supported as the long-lived sink:

| Variable | Description | Default |
| --- | --- | --- |
| `NEO4J_HOST` | Hostname used by the API containers. | `neo4j` |
| `NEO4J_PORT` | Bolt port exposed by Neo4j. | `7687` |
| `NEO4J_USER` / `NEO4J_PASSWORD` | Credentials with rights to create per-tenant databases. | `neo4j` / `neo4j_password` |
- **Neo4j** (default; the Docker Compose files already ship a `neo4j` service).
- **Amazon Neptune** (cloud-managed; opt-in).

Select the sink with `ATTACK_PATHS_SINK_DATABASE` (`neo4j` or `neptune`; default `neo4j`).

> Note: Cartography ingestion always uses a temporary Neo4j database, regardless of the configured sink. The `NEO4J_*` variables below must remain set even when `ATTACK_PATHS_SINK_DATABASE=neptune`.

### Neo4j sink
Comment thread
josema-xyz marked this conversation as resolved.

| Variable | Description | Default |
| --- | --- | --- |
| `NEO4J_HOST` | Hostname used by the API containers. | `neo4j` |
| `NEO4J_PORT` | Bolt port exposed by Neo4j. | `7687` |
| `NEO4J_USER` / `NEO4J_PASSWORD` | Credentials with rights to create per-tenant databases. | `neo4j` / `neo4j_password` |

### Neptune sink
Comment thread
josema-xyz marked this conversation as resolved.

| Variable | Description | Default |
| --- | --- | --- |
| `NEPTUNE_WRITER_ENDPOINT` | Bolt host for the Neptune writer instance. Required when sink is `neptune`. | _empty_ |
| `NEPTUNE_READER_ENDPOINT` | Optional reader endpoint for read-only queries. Falls back to the writer when unset. | _empty_ |
| `NEPTUNE_PORT` | Bolt port exposed by Neptune. | `8182` |
| `AWS_REGION` | Region the Neptune cluster lives in. Required when sink is `neptune`. | _empty_ |

Neptune authenticates with SigV4 using the standard boto3 credential chain. The worker's IAM role (or `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`) supplies the credentials. There is no Neptune password variable.

Every AWS provider scan will enqueue an Attack Paths ingestion job automatically. Other cloud providers will be added in future iterations.

Expand Down
10 changes: 9 additions & 1 deletion api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to the **Prowler API** are documented in this file.

## [1.32.0] (Prowler UNRELEASED)

### 🔄 Changed

- Attack Paths: AWS Neptune is now supported as a persistent sink database, selectable via `ATTACK_PATHS_SINK_DATABASE=neptune` (default `neo4j`), Cartography's per-scan staging database stays on Neo4j [(#11524)](https://github.com/prowler-cloud/prowler/pull/11524)

---

## [1.31.1] (Prowler UNRELEASED)


Expand Down Expand Up @@ -86,7 +94,7 @@ All notable changes to the **Prowler API** are documented in this file.
### 🚀 Added

- GIN index on `findings(categories, resource_services, resource_regions, resource_types)` to speed up `/api/v1/finding-groups` array filters [(#11001)](https://github.com/prowler-cloud/prowler/pull/11001)
- `GET /health/live` and `GET /health/ready` Kubernetes-style probe endpoints following the IETF Health Check Response Format (`application/health+json`). Readiness verifies PostgreSQL, Valkey and Neo4j connectivity and returns 503 with per-dependency detail when any is unreachable [(#11200)](https://github.com/prowler-cloud/prowler/pull/11200)
- `GET /health/live` and `GET /health/ready` Kubernetes-style probe endpoints following the IETF Health Check Response Format (`application/health+json`). Readiness verifies PostgreSQL, Valkey and Neo4j connectivity and returns 503 with per-dependency detail when any is unreachable; both endpoints centralize the API version on `config/version.py` (read from `pyproject.toml`) and are wired into the Helm charts and the Docker Compose healthcheck [(#11200)](https://github.com/prowler-cloud/prowler/pull/11200)

### 🔄 Changed

Expand Down
8 changes: 4 additions & 4 deletions api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ dependencies = [
"matplotlib (==3.10.8)",
"reportlab (==4.4.10)",
"neo4j (==6.1.0)",
"cartography (==0.135.0)",
"cartography (==0.136.0)",
"gevent (==25.9.1)",
"werkzeug (==3.1.7)",
"sqlparse (==0.5.5)",
Expand Down Expand Up @@ -174,7 +174,7 @@ constraint-dependencies = [
"blinker==1.9.0",
"boto3==1.40.61",
"botocore==1.40.61",
"cartography==0.135.0",
"cartography==0.136.0",
"celery==5.6.2",
"certifi==2026.1.4",
"cffi==2.0.0",
Expand Down Expand Up @@ -425,7 +425,7 @@ constraint-dependencies = [
"wcwidth==0.5.3",
"websocket-client==1.9.0",
"werkzeug==3.1.7",
"workos==6.0.4",
"workos==6.0.8",
"wrapt==1.17.3",
"xlsxwriter==3.2.9",
"xmlsec==1.3.17",
Expand All @@ -436,7 +436,7 @@ constraint-dependencies = [
"zope-interface==8.2",
"zstd==1.5.7.3"
]
# prowler@master needs okta==3.4.2; cartography 0.135.0 declares okta<1.0.0 for an
# prowler@master needs okta==3.4.2; cartography 0.136.0 declares okta<1.0.0 for an
# integration prowler does not import.
#
# prowler@master hard-pins microsoft-kiota-abstractions==1.9.2 in [project.dependencies].
Expand Down
3 changes: 0 additions & 3 deletions api/src/backend/api/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ def ready(self):
):
self._ensure_crypto_keys()

# Neo4j driver is created lazily on first use (see api.attack_paths.database).
# App init never contacts Neo4j, so a Neo4j outage cannot block API startup.

def _ensure_crypto_keys(self):
"""
Orchestrator method that ensures all required cryptographic keys are present.
Expand Down
26 changes: 12 additions & 14 deletions api/src/backend/api/attack_paths/cypher_sanitizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
Two responsibilities:

1. **Validation** - reject queries containing SSRF or dangerous procedure
patterns (defense-in-depth; the primary control is ``neo4j.READ_ACCESS``).
patterns (defense-in-depth; the primary control is `neo4j.READ_ACCESS`).

2. **Provider-scoped label injection** - inject a dynamic
``_Provider_{uuid}`` label into every node pattern so the database can
`_Provider_{uuid}` label into every node pattern so the database can
use its native label index for provider isolation.

Label-injection pipeline:
Expand All @@ -27,24 +27,24 @@


# Step 1 - String / comment protection
# Single combined regex: strings first, then line comments.
# Single combined regex: strings first, then line comments
# The regex engine finds the leftmost match, so a string like 'https://prowler.com'
# is consumed as a string before the // inside it can match as a comment.
# is consumed as a string before the // inside it can match as a comment
_PROTECTED_RE = re.compile(r"'(?:[^'\\]|\\.)*'|\"(?:[^\"\\]|\\.)*\"|//[^\n]*")

# Step 2 - Clause splitting
# OPTIONAL MATCH must come before MATCH to avoid partial matching.
# `OPTIONAL MATCH` must come before `MATCH` to avoid partial matching
_CLAUSE_RE = re.compile(
r"\b(OPTIONAL\s+MATCH|MATCH|WHERE|RETURN|WITH|ORDER\s+BY"
r"|SKIP|LIMIT|UNION|UNWIND|CALL)\b",
re.IGNORECASE,
)

# Pass A - Labeled node patterns (all segments)
# Matches node patterns that have at least one :Label.
# (?<!\w)\( - open paren NOT preceded by a word char (excludes function calls).
# Group 1: optional variable + one or more :Label
# Group 2: optional {properties} + closing paren
# Matches node patterns that have at least one `:Label`
# `(?<!\w)\(` - open paren NOT preceded by a word char, excludes function calls
# Group 1: optional variable + one or more `:Label`
# Group 2: optional `{`properties`}` + closing paren
_LABELED_NODE_RE = re.compile(
r"(?<!\w)\("
r"("
Expand All @@ -57,9 +57,9 @@
r")"
)

# Pass B - Bare node patterns (MATCH segments only)
# Matches (identifier) or (identifier {properties}) without any :Label.
# Only applied in MATCH/OPTIONAL MATCH segments.
# Pass B - Bare node patterns (`MATCH` segments only)
# Matches (identifier) or (identifier {properties}) without any `:Label`
# Only applied in `MATCH` / `OPTIONAL MATCH` segments
_BARE_NODE_RE = re.compile(
r"(?<!\w)\(" r"(\s*[a-zA-Z_]\w*)" r"(\s*(?:\{[^}]*\})?)" r"\s*\)"
)
Expand Down Expand Up @@ -136,9 +136,7 @@ def _save(match):
return work


# ---------------------------------------------------------------------------
# Validation
# ---------------------------------------------------------------------------

# Patterns that indicate SSRF or dangerous procedure calls
# Defense-in-depth layer - the primary control is `neo4j.READ_ACCESS`
Expand Down
Loading
Loading