mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-08-20 10:00:40 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7d96df300 | ||
|
|
5e60402469 |
@@ -158,7 +158,7 @@ SENTRY_RELEASE=local
|
||||
# REO_DEV_CLIENT_ID=
|
||||
|
||||
#### Prowler release version ####
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.40.0
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.37.0
|
||||
|
||||
# Social login credentials
|
||||
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# Generated by gh-aw and marked DO NOT EDIT. It uses concurrency options newer than
|
||||
# actionlint knows, so the findings are about actionlint's schema, not our workflows.
|
||||
paths:
|
||||
.github/workflows/**/*.lock.yml:
|
||||
ignore:
|
||||
- '.*'
|
||||
@@ -1,179 +0,0 @@
|
||||
name: 'Container Security Scan with Grype'
|
||||
description: 'Scans container images for vulnerabilities using Grype and reports results'
|
||||
author: 'Prowler'
|
||||
|
||||
inputs:
|
||||
image-name:
|
||||
description: 'Container image name to scan'
|
||||
required: true
|
||||
image-tag:
|
||||
description: 'Container image tag to scan'
|
||||
required: true
|
||||
default: ${{ github.sha }}
|
||||
fail-on-severity:
|
||||
description: 'Fail the build on findings at this severity or above: critical, high, or none'
|
||||
required: false
|
||||
default: 'high'
|
||||
upload-sarif:
|
||||
description: 'Upload results to GitHub Security tab'
|
||||
required: false
|
||||
default: 'true'
|
||||
create-pr-comment:
|
||||
description: 'Create a comment on the PR with scan results'
|
||||
required: false
|
||||
default: 'true'
|
||||
artifact-retention-days:
|
||||
description: 'Days to retain the Grype report artifact'
|
||||
required: false
|
||||
default: '2'
|
||||
|
||||
outputs:
|
||||
critical-count:
|
||||
description: 'Number of critical vulnerabilities found'
|
||||
value: ${{ steps.security-check.outputs.critical }}
|
||||
high-count:
|
||||
description: 'Number of high vulnerabilities found'
|
||||
value: ${{ steps.security-check.outputs.high }}
|
||||
total-count:
|
||||
description: 'Total number of vulnerabilities found'
|
||||
value: ${{ steps.security-check.outputs.total }}
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Run Grype vulnerability scan (JSON)
|
||||
uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0
|
||||
with:
|
||||
image: ${{ inputs.image-name }}:${{ inputs.image-tag }}
|
||||
output-format: 'json'
|
||||
output-file: 'grype-report.json'
|
||||
fail-build: 'false'
|
||||
by-cve: 'true' # Report CVE ids rather than GHSA, so findings line up with Trivy's
|
||||
only-fixed: 'true' # A finding with no available fix is not actionable, so it must not gate
|
||||
cache-db: 'true'
|
||||
grype-version: 'v0.116.1'
|
||||
|
||||
- name: Run Grype vulnerability scan (SARIF)
|
||||
if: inputs.upload-sarif == 'true' && github.event_name == 'push'
|
||||
uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0
|
||||
with:
|
||||
image: ${{ inputs.image-name }}:${{ inputs.image-tag }}
|
||||
output-format: 'sarif'
|
||||
output-file: 'grype-results.sarif'
|
||||
fail-build: 'false'
|
||||
severity-cutoff: 'high'
|
||||
by-cve: 'true'
|
||||
only-fixed: 'true' # A finding with no available fix is not actionable, so it must not gate
|
||||
cache-db: 'true'
|
||||
grype-version: 'v0.116.1'
|
||||
|
||||
- name: Upload Grype results to GitHub Security tab
|
||||
if: inputs.upload-sarif == 'true' && github.event_name == 'push'
|
||||
uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
|
||||
with:
|
||||
sarif_file: 'grype-results.sarif'
|
||||
category: 'grype-container'
|
||||
|
||||
- name: Upload Grype report artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: always()
|
||||
with:
|
||||
name: grype-scan-report-${{ inputs.image-name }}-${{ inputs.image-tag }}
|
||||
path: grype-report.json
|
||||
retention-days: ${{ inputs.artifact-retention-days }}
|
||||
|
||||
- name: Generate security summary
|
||||
id: security-check
|
||||
shell: bash
|
||||
run: |
|
||||
CRITICAL=$(jq '[.matches[]? | select(.vulnerability.severity=="Critical")] | length' grype-report.json)
|
||||
HIGH=$(jq '[.matches[]? | select(.vulnerability.severity=="High")] | length' grype-report.json)
|
||||
TOTAL=$(jq '[.matches[]?] | length' grype-report.json)
|
||||
|
||||
echo "critical=$CRITICAL" >> $GITHUB_OUTPUT
|
||||
echo "high=$HIGH" >> $GITHUB_OUTPUT
|
||||
echo "total=$TOTAL" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "### 🔎 Container Security Scan (Grype)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Image:** \`${INPUTS_IMAGE_NAME}:${INPUTS_IMAGE_TAG}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- 🔴 Critical: $CRITICAL" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- 🟠 High: $HIGH" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Total**: $TOTAL" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Reported alongside Trivy, not instead of it. Counts differ by design." >> $GITHUB_STEP_SUMMARY
|
||||
env:
|
||||
INPUTS_IMAGE_NAME: ${{ inputs.image-name }}
|
||||
INPUTS_IMAGE_TAG: ${{ inputs.image-tag }}
|
||||
|
||||
# Before the gate, so the comment is there to explain a failure rather than absent because of it
|
||||
- name: Comment scan results on PR
|
||||
if: >-
|
||||
inputs.create-pr-comment == 'true'
|
||||
&& github.event_name == 'pull_request'
|
||||
&& github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
IMAGE_NAME: ${{ inputs.image-name }}
|
||||
GITHUB_SHA: ${{ inputs.image-tag }}
|
||||
CUTOFF: ${{ inputs.fail-on-severity }}
|
||||
with:
|
||||
script: |
|
||||
const comment = require('./.github/scripts/grype-pr-comment.js');
|
||||
|
||||
// Unique identifier to find our comment
|
||||
const marker = `<!-- grype-scan-comment:${process.env.IMAGE_NAME} -->`;
|
||||
const body = marker + '\n' + comment;
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
|
||||
const existingComment = comments.find(c => c.body?.includes(marker));
|
||||
|
||||
if (existingComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existingComment.id,
|
||||
body: body
|
||||
});
|
||||
console.log('✅ Updated existing Grype scan comment');
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: body
|
||||
});
|
||||
console.log('✅ Created new Grype scan comment');
|
||||
}
|
||||
|
||||
- name: Check for blocking vulnerabilities
|
||||
if: inputs.fail-on-severity != 'none'
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$CUTOFF" = "critical" ]; then
|
||||
BLOCKING=$CRITICAL
|
||||
SEVERITIES='["Critical"]'
|
||||
else
|
||||
BLOCKING=$((CRITICAL + HIGH))
|
||||
SEVERITIES='["Critical","High"]'
|
||||
fi
|
||||
|
||||
if [ "$BLOCKING" -gt 0 ]; then
|
||||
echo "::error::Found $BLOCKING vulnerabilities at severity ${CUTOFF} or above ($CRITICAL critical, $HIGH high)"
|
||||
echo "::warning::Update the package, or add it to .grype.yaml with a reason if nothing can be done"
|
||||
jq -r --argjson severities "$SEVERITIES" \
|
||||
'.matches[] | select(.vulnerability.severity | IN($severities[]))
|
||||
| " \(.vulnerability.severity)\t\(.vulnerability.id)\t\(.artifact.name) \(.artifact.version)"' \
|
||||
grype-report.json | sort -u
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
CUTOFF: ${{ inputs.fail-on-severity }}
|
||||
CRITICAL: ${{ steps.security-check.outputs.critical }}
|
||||
HIGH: ${{ steps.security-check.outputs.high }}
|
||||
@@ -14,10 +14,10 @@ inputs:
|
||||
description: 'Severities to scan for (comma-separated)'
|
||||
required: false
|
||||
default: 'CRITICAL,HIGH,MEDIUM,LOW'
|
||||
fail-on-severity:
|
||||
description: 'Fail the build on findings at this severity or above: critical, high, or none'
|
||||
fail-on-critical:
|
||||
description: 'Fail the build if critical vulnerabilities are found'
|
||||
required: false
|
||||
default: 'high'
|
||||
default: 'false'
|
||||
upload-sarif:
|
||||
description: 'Upload results to GitHub Security tab'
|
||||
required: false
|
||||
@@ -62,12 +62,8 @@ runs:
|
||||
severity: ${{ inputs.severity }}
|
||||
exit-code: '0'
|
||||
scanners: 'vuln'
|
||||
ignore-unfixed: 'true' # A finding with no available fix is not actionable, so it must not gate
|
||||
timeout: '5m'
|
||||
version: 'v0.73.0'
|
||||
# Not trivyignores: that input drops the .yaml extension Trivy parses by.
|
||||
env:
|
||||
TRIVY_IGNOREFILE: '.trivyignore.yaml'
|
||||
version: 'v0.71.2'
|
||||
|
||||
- name: Run Trivy vulnerability scan (SARIF)
|
||||
if: inputs.upload-sarif == 'true' && github.event_name == 'push'
|
||||
@@ -79,12 +75,8 @@ runs:
|
||||
severity: 'CRITICAL,HIGH'
|
||||
exit-code: '0'
|
||||
scanners: 'vuln'
|
||||
ignore-unfixed: 'true' # A finding with no available fix is not actionable, so it must not gate
|
||||
timeout: '5m'
|
||||
version: 'v0.73.0'
|
||||
# Not trivyignores: that input drops the .yaml extension Trivy parses by.
|
||||
env:
|
||||
TRIVY_IGNOREFILE: '.trivyignore.yaml'
|
||||
version: 'v0.71.2'
|
||||
|
||||
- name: Upload Trivy results to GitHub Security tab
|
||||
if: inputs.upload-sarif == 'true' && github.event_name == 'push'
|
||||
@@ -171,28 +163,13 @@ runs:
|
||||
console.log('✅ Created new Trivy scan comment');
|
||||
}
|
||||
|
||||
- name: Check for blocking vulnerabilities
|
||||
if: inputs.fail-on-severity != 'none'
|
||||
- name: Check for critical vulnerabilities
|
||||
if: inputs.fail-on-critical == 'true' && steps.security-check.outputs.critical != '0'
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$CUTOFF" = "critical" ]; then
|
||||
BLOCKING=$CRITICAL
|
||||
SEVERITIES='["CRITICAL"]'
|
||||
else
|
||||
BLOCKING=$((CRITICAL + HIGH))
|
||||
SEVERITIES='["CRITICAL","HIGH"]'
|
||||
fi
|
||||
echo "::error::Found ${STEPS_SECURITY_CHECK_OUTPUTS_CRITICAL} critical vulnerabilities"
|
||||
echo "::warning::Please update packages or use a different base image"
|
||||
exit 1
|
||||
|
||||
if [ "$BLOCKING" -gt 0 ]; then
|
||||
echo "::error::Found $BLOCKING vulnerabilities at severity ${CUTOFF} or above ($CRITICAL critical, $HIGH high)"
|
||||
echo "::warning::Update the package, or add it to .trivyignore.yaml with a reason if nothing can be done"
|
||||
jq -r --argjson severities "$SEVERITIES" \
|
||||
'.Results[]?.Vulnerabilities[]? | select(.Severity | IN($severities[]))
|
||||
| " \(.Severity)\t\(.VulnerabilityID)\t\(.PkgName) \(.InstalledVersion)"' \
|
||||
trivy-report.json | sort -u
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
CUTOFF: ${{ inputs.fail-on-severity }}
|
||||
CRITICAL: ${{ steps.security-check.outputs.critical }}
|
||||
HIGH: ${{ steps.security-check.outputs.high }}
|
||||
STEPS_SECURITY_CHECK_OUTPUTS_CRITICAL: ${{ steps.security-check.outputs.critical }}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
// Configuration from environment variables
|
||||
const REPORT_FILE = process.env.GRYPE_REPORT_FILE || 'grype-report.json';
|
||||
const IMAGE_NAME = process.env.IMAGE_NAME || 'container-image';
|
||||
const GITHUB_SHA = process.env.GITHUB_SHA || 'unknown';
|
||||
const GITHUB_REPOSITORY = process.env.GITHUB_REPOSITORY || '';
|
||||
const GITHUB_RUN_ID = process.env.GITHUB_RUN_ID || '';
|
||||
const CUTOFF = process.env.CUTOFF || 'high';
|
||||
|
||||
// A cutoff of 'critical' blocks only on critical; anything else blocks on high and above
|
||||
const blocking = CUTOFF === 'critical' ? ['Critical'] : ['Critical', 'High'];
|
||||
|
||||
const report = JSON.parse(fs.readFileSync(REPORT_FILE, 'utf-8'));
|
||||
const matches = Array.isArray(report.matches) ? report.matches : [];
|
||||
const ignored = Array.isArray(report.ignoredMatches) ? report.ignoredMatches : [];
|
||||
|
||||
const counts = { Critical: 0, High: 0, Medium: 0, Low: 0, Negligible: 0, Unknown: 0 };
|
||||
const blockers = new Map();
|
||||
|
||||
for (const match of matches) {
|
||||
const severity = match.vulnerability.severity;
|
||||
if (counts[severity] !== undefined) {
|
||||
counts[severity]++;
|
||||
}
|
||||
if (blocking.includes(severity)) {
|
||||
const artifact = match.artifact;
|
||||
const fixedIn = (match.vulnerability.fix && match.vulnerability.fix.versions || []).join(', ');
|
||||
// Same CVE can match several install paths of one package; collapse them
|
||||
blockers.set(`${match.vulnerability.id}|${artifact.name}`, {
|
||||
id: match.vulnerability.id,
|
||||
severity,
|
||||
name: artifact.name,
|
||||
version: artifact.version,
|
||||
fixedIn
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const ignoredBlocking = ignored.filter(m => blocking.includes(m.vulnerability.severity)).length;
|
||||
const shortSha = GITHUB_SHA.substring(0, 7);
|
||||
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19) + ' UTC';
|
||||
|
||||
const severityConfig = {
|
||||
Critical: { icon: '🔴', label: 'Critical' },
|
||||
High: { icon: '🟠', label: 'High' },
|
||||
Medium: { icon: '🟡', label: 'Medium' },
|
||||
Low: { icon: '🔵', label: 'Low' }
|
||||
};
|
||||
|
||||
let comment = '## 🔎 Container Security Scan (Grype)\n\n';
|
||||
comment += `**Image:** \`${IMAGE_NAME}:${shortSha}\`\n`;
|
||||
comment += `**Last scan:** ${timestamp}\n\n`;
|
||||
|
||||
if (blockers.size === 0) {
|
||||
comment += '### ✅ Nothing Blocking\n\n';
|
||||
comment += `No findings at **${blocking.join(' or ').toLowerCase()}** severity.\n`;
|
||||
} else {
|
||||
comment += `### ⚠️ ${blockers.size} Finding(s) Blocking This PR\n\n`;
|
||||
comment += '| Severity | CVE | Package | Installed | Fixed in |\n';
|
||||
comment += '|---|---|---|---|---|\n';
|
||||
|
||||
const order = { Critical: 0, High: 1 };
|
||||
const rows = [...blockers.values()].sort((a, b) =>
|
||||
(order[a.severity] - order[b.severity]) || a.name.localeCompare(b.name));
|
||||
|
||||
for (const row of rows) {
|
||||
const config = severityConfig[row.severity];
|
||||
comment += `| ${config.icon} ${config.label} | \`${row.id}\` | \`${row.name}\` | ${row.version} | ${row.fixedIn || '—'} |\n`;
|
||||
}
|
||||
|
||||
comment += '\n**What to do:**\n';
|
||||
comment += '- Upgrade the package to the version in the "Fixed in" column.\n';
|
||||
comment += '- If it is pinned by another dependency, or the fix is otherwise out of reach, add it to `.grype.yaml` **with the reason**.\n';
|
||||
comment += '- Findings with no published fix never appear here: the scan runs with `only-fixed`, so it reports only what can actually be acted on.\n';
|
||||
}
|
||||
|
||||
const otherCounts = Object.entries(counts)
|
||||
.filter(([severity, count]) => !blocking.includes(severity) && count > 0)
|
||||
.map(([severity, count]) => `${severity.toLowerCase()}: ${count}`);
|
||||
|
||||
if (otherCounts.length > 0) {
|
||||
comment += `\nNot blocking at this cutoff — ${otherCounts.join(', ')}.\n`;
|
||||
}
|
||||
|
||||
if (ignoredBlocking > 0) {
|
||||
comment += `\n${ignoredBlocking} finding(s) excluded by \`.grype.yaml\`, each with a documented reason.\n`;
|
||||
}
|
||||
|
||||
comment += '\n---\n';
|
||||
comment += '📋 **Resources:**\n';
|
||||
|
||||
if (GITHUB_REPOSITORY && GITHUB_RUN_ID) {
|
||||
comment += `- [Download full report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}) (see artifacts)\n`;
|
||||
}
|
||||
|
||||
comment += '- [View in Security tab](https://github.com/' + (GITHUB_REPOSITORY || 'repository') + '/security/code-scanning)\n';
|
||||
comment += '- Scanned with [Grype](https://github.com/anchore/grype), alongside Trivy\n';
|
||||
|
||||
module.exports = comment;
|
||||
@@ -42,7 +42,6 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
short-sha: ${{ steps.set-short-sha.outputs.short-sha }}
|
||||
created: ${{ steps.set-short-sha.outputs.created }}
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
@@ -53,9 +52,7 @@ jobs:
|
||||
|
||||
- name: Calculate short SHA
|
||||
id: set-short-sha
|
||||
run: |
|
||||
echo "short-sha=${GITHUB_SHA::7}" >> "${GITHUB_OUTPUT}"
|
||||
echo "created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "${GITHUB_OUTPUT}"
|
||||
run: echo "short-sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
|
||||
|
||||
notify-release-started:
|
||||
if: github.repository == 'prowler-cloud/prowler' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
||||
@@ -162,20 +159,9 @@ jobs:
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
push: true
|
||||
sbom: true
|
||||
# max, not the default min: min records little beyond the build ref.
|
||||
provenance: mode=max
|
||||
platforms: ${{ matrix.platform }}
|
||||
tags: |
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.short-sha }}-${{ matrix.arch }}
|
||||
labels: |
|
||||
org.opencontainers.image.title=Prowler Local Server API
|
||||
org.opencontainers.image.description=API for Prowler Local Server (Django/DRF)
|
||||
org.opencontainers.image.vendor=ProwlerPro, Inc.
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.created=${{ needs.setup.outputs.created }}
|
||||
${{ (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && format('org.opencontainers.image.version={0}', env.RELEASE_TAG) || '' }}
|
||||
cache-from: type=gha,scope=${{ matrix.arch }}
|
||||
cache-to: type=gha,mode=${{ github.event_name == 'pull_request' && 'min' || 'max' }},scope=${{ matrix.arch }}
|
||||
|
||||
@@ -193,12 +179,12 @@ jobs:
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
auth.docker.io:443
|
||||
github.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
registry-1.docker.io:443
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
registry-1.docker.io:443
|
||||
release-assets.githubusercontent.com:443
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
@@ -210,9 +196,9 @@ jobs:
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.LATEST_TAG }} \
|
||||
-t "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}" \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-amd64" \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-arm64"
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA} \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-amd64 \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-arm64
|
||||
env:
|
||||
NEEDS_SETUP_OUTPUTS_SHORT_SHA: ${{ needs.setup.outputs.short-sha }}
|
||||
|
||||
@@ -220,10 +206,10 @@ jobs:
|
||||
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${RELEASE_TAG}" \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${RELEASE_TAG} \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.STABLE_TAG }} \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-amd64" \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-arm64"
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-amd64 \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-arm64
|
||||
env:
|
||||
NEEDS_SETUP_OUTPUTS_SHORT_SHA: ${{ needs.setup.outputs.short-sha }}
|
||||
|
||||
@@ -263,9 +249,9 @@ jobs:
|
||||
id: outcome
|
||||
run: |
|
||||
if [[ "${NEEDS_CONTAINER_BUILD_PUSH_RESULT}" == "success" && "${NEEDS_CREATE_MANIFEST_RESULT}" == "success" ]]; then
|
||||
echo "outcome=success" >> "$GITHUB_OUTPUT"
|
||||
echo "outcome=success" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "outcome=failure" >> "$GITHUB_OUTPUT"
|
||||
echo "outcome=failure" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
NEEDS_CONTAINER_BUILD_PUSH_RESULT: ${{ needs.container-build-push.result }}
|
||||
|
||||
@@ -81,10 +81,6 @@ jobs:
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
raw.githubusercontent.com:443
|
||||
objects.githubusercontent.com:443
|
||||
grype.anchore.io:443
|
||||
get.anchore.io:443
|
||||
debian.map.fastlydns.net:80
|
||||
release-assets.githubusercontent.com:443
|
||||
objects.githubusercontent.com:443
|
||||
@@ -111,11 +107,7 @@ jobs:
|
||||
with:
|
||||
files: |
|
||||
api/**
|
||||
.trivyignore.yaml
|
||||
.github/actions/trivy-scan/**
|
||||
.github/actions/grype-scan/**
|
||||
.grype.yaml
|
||||
.github/scripts/grype-pr-comment.js
|
||||
files_ignore: |
|
||||
api/docs/**
|
||||
api/README.md
|
||||
@@ -153,13 +145,5 @@ jobs:
|
||||
with:
|
||||
image-name: ${{ env.IMAGE_NAME }}
|
||||
image-tag: ${{ github.sha }}
|
||||
fail-on-severity: 'high'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
|
||||
- name: Scan container with Grype
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/grype-scan
|
||||
with:
|
||||
image-name: ${{ env.IMAGE_NAME }}
|
||||
image-tag: ${{ github.sha }}
|
||||
fail-on-severity: 'high'
|
||||
fail-on-critical: 'true'
|
||||
severity: 'CRITICAL'
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
name: 'CI: Actionlint'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
schedule:
|
||||
- cron: '45 06 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
actionlint:
|
||||
if: github.repository == 'prowler-cloud/prowler'
|
||||
name: GitHub Actions Schema Check
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
github.com:443
|
||||
api.github.com:443
|
||||
auth.docker.io:443
|
||||
registry-1.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
|
||||
# Always runs so it always reports; the lint is skipped when nothing changed.
|
||||
- name: Check for workflow changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: .github/**
|
||||
|
||||
# SC2129 is style only: it suggests grouping consecutive redirects.
|
||||
- name: Run actionlint
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
env:
|
||||
SHELLCHECK_OPTS: '-e SC2129'
|
||||
run: |
|
||||
docker run --rm -v "$PWD:/repo" --workdir /repo -e SHELLCHECK_OPTS \
|
||||
rhysd/actionlint:1.7.12@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667 \
|
||||
-color
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
run: |
|
||||
echo "Removing 'status/awaiting-response' label from #$ISSUE_NUMBER"
|
||||
gh api "/repos/${{ github.repository }}/issues/$ISSUE_NUMBER/labels/status%2Fawaiting-response" \
|
||||
gh api /repos/${{ github.repository }}/issues/$ISSUE_NUMBER/labels/status%2Fawaiting-response \
|
||||
-X DELETE
|
||||
|
||||
- name: Add 'status/waiting-for-revision' label
|
||||
@@ -41,6 +41,6 @@ jobs:
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
run: |
|
||||
echo "Adding 'status/waiting-for-revision' label to #$ISSUE_NUMBER"
|
||||
gh api "/repos/${{ github.repository }}/issues/$ISSUE_NUMBER/labels" \
|
||||
gh api /repos/${{ github.repository }}/issues/$ISSUE_NUMBER/labels \
|
||||
-X POST \
|
||||
-f labels[]='status/waiting-for-revision'
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
name: 'Tools: Sync Docker Hub Descriptions'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
paths:
|
||||
- 'docs/dockerhub/README.md'
|
||||
- '.github/workflows/dockerhub-descriptions.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
OVERVIEW_FILE: docs/dockerhub/README.md
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
prowlercloud:
|
||||
if: github.repository == 'prowler-cloud/prowler' && github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- repository: prowlercloud/prowler
|
||||
short_description: 'Prowler CLI: the Open Cloud Security tool for AWS, Azure, Google Cloud, Kubernetes, M365 and GitHub'
|
||||
- repository: prowlercloud/prowler-api
|
||||
short_description: 'Prowler Local Server - API: the JSON API and Task Runner components of Prowler'
|
||||
- repository: prowlercloud/prowler-ui
|
||||
short_description: 'Prowler Local Server - UI: the web interface to run Prowler scans and explore findings'
|
||||
- repository: prowlercloud/prowler-mcp
|
||||
short_description: 'Prowler MCP: the interface for agents, including IDE plugins and agent integrations'
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
github.com:443
|
||||
hub.docker.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Update Docker Hub description for ${{ matrix.repository }}
|
||||
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
repository: ${{ matrix.repository }}
|
||||
short-description: ${{ matrix.short_description }}
|
||||
readme-filepath: ${{ env.OVERVIEW_FILE }}
|
||||
|
||||
toniblyx:
|
||||
if: github.repository == 'prowler-cloud/prowler' && github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
github.com:443
|
||||
hub.docker.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Update Docker Hub description for toniblyx/prowler
|
||||
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0
|
||||
with:
|
||||
username: ${{ secrets.TONIBLYX_DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.TONIBLYX_DOCKERHUB_PASSWORD }}
|
||||
repository: toniblyx/prowler
|
||||
short-description: 'Prowler CLI (legacy repository, mirrors prowlercloud/prowler)'
|
||||
readme-filepath: ${{ env.OVERVIEW_FILE }}
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
|
||||
- name: Login to GHCR
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u "${GITHUB_ACTOR}" --password-stdin
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${GITHUB_ACTOR} --password-stdin
|
||||
|
||||
- name: Update chart dependencies
|
||||
run: helm dependency update ${{ env.CHART_PATH }}
|
||||
|
||||
@@ -85,10 +85,10 @@ jobs:
|
||||
|
||||
# Check if author is in the org members list
|
||||
if printf '%s\n' "${ORG_MEMBERS[@]}" | grep -q "^${AUTHOR}$"; then
|
||||
echo "is_member=true" >> "$GITHUB_OUTPUT"
|
||||
echo "is_member=true" >> $GITHUB_OUTPUT
|
||||
echo "$AUTHOR is an organization member"
|
||||
else
|
||||
echo "is_member=false" >> "$GITHUB_OUTPUT"
|
||||
echo "is_member=false" >> $GITHUB_OUTPUT
|
||||
echo "$AUTHOR is not an organization member"
|
||||
fi
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
short-sha: ${{ steps.set-short-sha.outputs.short-sha }}
|
||||
created: ${{ steps.set-short-sha.outputs.created }}
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
@@ -52,9 +51,7 @@ jobs:
|
||||
|
||||
- name: Calculate short SHA
|
||||
id: set-short-sha
|
||||
run: |
|
||||
echo "short-sha=${GITHUB_SHA::7}" >> "${GITHUB_OUTPUT}"
|
||||
echo "created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "${GITHUB_OUTPUT}"
|
||||
run: echo "short-sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
|
||||
|
||||
notify-release-started:
|
||||
if: github.repository == 'prowler-cloud/prowler' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
||||
@@ -113,15 +110,15 @@ jobs:
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
auth.docker.io:443
|
||||
files.pythonhosted.org:443
|
||||
ghcr.io:443
|
||||
github.com:443
|
||||
pkg-containers.githubusercontent.com:443
|
||||
registry-1.docker.io:443
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
ghcr.io:443
|
||||
pkg-containers.githubusercontent.com:443
|
||||
files.pythonhosted.org:443
|
||||
pypi.org:443
|
||||
registry-1.docker.io:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -144,20 +141,17 @@ jobs:
|
||||
with:
|
||||
context: ${{ env.WORKING_DIRECTORY }}
|
||||
push: true
|
||||
sbom: true
|
||||
# max, not the default min: min records little beyond the build ref.
|
||||
provenance: mode=max
|
||||
platforms: ${{ matrix.platform }}
|
||||
tags: |
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.short-sha }}-${{ matrix.arch }}
|
||||
labels: |
|
||||
org.opencontainers.image.title=Prowler MCP
|
||||
org.opencontainers.image.title=Prowler MCP Server
|
||||
org.opencontainers.image.description=Model Context Protocol server for Prowler
|
||||
org.opencontainers.image.vendor=ProwlerPro, Inc.
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.created=${{ needs.setup.outputs.created }}
|
||||
${{ (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && format('org.opencontainers.image.version={0}', env.RELEASE_TAG) || '' }}
|
||||
org.opencontainers.image.created=${{ github.event_name == 'release' && github.event.release.published_at || github.event.head_commit.timestamp }}
|
||||
${{ github.event_name == 'release' && format('org.opencontainers.image.version={0}', env.RELEASE_TAG) || '' }}
|
||||
cache-from: type=gha,scope=${{ matrix.arch }}
|
||||
cache-to: type=gha,mode=${{ github.event_name == 'pull_request' && 'min' || 'max' }},scope=${{ matrix.arch }}
|
||||
|
||||
@@ -175,11 +169,11 @@ jobs:
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
registry-1.docker.io:443
|
||||
auth.docker.io:443
|
||||
github.com:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
registry-1.docker.io:443
|
||||
github.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
|
||||
- name: Login to DockerHub
|
||||
@@ -193,9 +187,9 @@ jobs:
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.LATEST_TAG }} \
|
||||
-t "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}" \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-amd64" \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-arm64"
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA} \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-amd64 \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-arm64
|
||||
env:
|
||||
NEEDS_SETUP_OUTPUTS_SHORT_SHA: ${{ needs.setup.outputs.short-sha }}
|
||||
|
||||
@@ -203,10 +197,10 @@ jobs:
|
||||
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${RELEASE_TAG}" \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${RELEASE_TAG} \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.STABLE_TAG }} \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-amd64" \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-arm64"
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-amd64 \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-arm64
|
||||
env:
|
||||
NEEDS_SETUP_OUTPUTS_SHORT_SHA: ${{ needs.setup.outputs.short-sha }}
|
||||
|
||||
@@ -246,9 +240,9 @@ jobs:
|
||||
id: outcome
|
||||
run: |
|
||||
if [[ "${NEEDS_CONTAINER_BUILD_PUSH_RESULT}" == "success" && "${NEEDS_CREATE_MANIFEST_RESULT}" == "success" ]]; then
|
||||
echo "outcome=success" >> "$GITHUB_OUTPUT"
|
||||
echo "outcome=success" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "outcome=failure" >> "$GITHUB_OUTPUT"
|
||||
echo "outcome=failure" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
NEEDS_CONTAINER_BUILD_PUSH_RESULT: ${{ needs.container-build-push.result }}
|
||||
|
||||
@@ -87,9 +87,6 @@ jobs:
|
||||
get.trivy.dev:443
|
||||
release-assets.githubusercontent.com:443
|
||||
objects.githubusercontent.com:443
|
||||
raw.githubusercontent.com:443
|
||||
grype.anchore.io:443
|
||||
get.anchore.io:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -103,11 +100,7 @@ jobs:
|
||||
with:
|
||||
files: |
|
||||
mcp_server/**
|
||||
.trivyignore.yaml
|
||||
.github/actions/trivy-scan/**
|
||||
.github/actions/grype-scan/**
|
||||
.grype.yaml
|
||||
.github/scripts/grype-pr-comment.js
|
||||
files_ignore: |
|
||||
mcp_server/README.md
|
||||
mcp_server/CHANGELOG.md
|
||||
@@ -134,13 +127,5 @@ jobs:
|
||||
with:
|
||||
image-name: ${{ env.IMAGE_NAME }}
|
||||
image-tag: ${{ github.sha }}
|
||||
fail-on-severity: 'high'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
|
||||
- name: Scan MCP container with Grype
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/grype-scan
|
||||
with:
|
||||
image-name: ${{ env.IMAGE_NAME }}
|
||||
image-tag: ${{ github.sha }}
|
||||
fail-on-severity: 'high'
|
||||
fail-on-critical: 'true'
|
||||
severity: 'CRITICAL'
|
||||
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
|
||||
- name: Publish prowler-mcp package to PyPI
|
||||
if: steps.pypi-check.outputs.skip != 'true'
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
with:
|
||||
packages-dir: ${{ env.WORKING_DIRECTORY }}/dist/
|
||||
print-hash: true
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
name: 'MCP: Tests'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'v5.*'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'v5.*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
MCP_WORKING_DIR: ./mcp_server
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
mcp-tests:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
# requires-python is >=3.12 while the shipped image is 3.13; testing both
|
||||
# is what keeps that floor honest.
|
||||
python-version:
|
||||
- '3.12'
|
||||
- '3.13'
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./mcp_server
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: block
|
||||
# hub.prowler.com and raw.githubusercontent.com are deliberately absent:
|
||||
# the suite mocks every outbound call, so a real one must fail the job.
|
||||
# The sentry.io entry is not the test suite: the Codecov uploader sends
|
||||
# its own telemetry there, so api-tests.yml and sdk-tests.yml allow it too.
|
||||
allowed-endpoints: >
|
||||
github.com:443
|
||||
pypi.org:443
|
||||
files.pythonhosted.org:443
|
||||
cli.codecov.io:443
|
||||
keybase.io:443
|
||||
ingest.codecov.io:443
|
||||
o26192.ingest.us.sentry.io:443
|
||||
storage.googleapis.com:443
|
||||
api.github.com:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# zizmor: ignore[artipacked]
|
||||
persist-credentials: true # Required by tj-actions/changed-files to fetch PR branch
|
||||
|
||||
- name: Check for MCP server changes
|
||||
id: check-changes
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
mcp_server/**
|
||||
.github/workflows/mcp-tests.yml
|
||||
codecov.yml
|
||||
files_ignore: |
|
||||
mcp_server/README.md
|
||||
mcp_server/CHANGELOG.md
|
||||
mcp_server/changelog.d/**
|
||||
mcp_server/AGENTS.md
|
||||
mcp_server/Dockerfile
|
||||
mcp_server/.dockerignore
|
||||
mcp_server/entrypoint.sh
|
||||
|
||||
- name: Setup Python with uv
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/setup-python-uv
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
working-directory: ./mcp_server
|
||||
|
||||
- name: Run tests with pytest
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
run: uv run pytest --cov=./prowler_mcp_server --cov-report=xml tests
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
flags: mcp
|
||||
@@ -129,6 +129,7 @@ jobs:
|
||||
handwritten_changelogs=""
|
||||
|
||||
all_changed=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}" | tr ' ' '\n')
|
||||
added=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ADDED_FILES}" | tr ' ' '\n')
|
||||
added_or_renamed=$(printf '%s\n%s' "${STEPS_CHANGED_FILES_OUTPUTS_ADDED_FILES}" "${STEPS_CHANGED_FILES_OUTPUTS_RENAMED_FILES}" | tr ' ' '\n')
|
||||
added_modified_or_renamed=$(printf '%s\n%s\n%s' "${STEPS_CHANGED_FILES_OUTPUTS_ADDED_FILES}" "${STEPS_CHANGED_FILES_OUTPUTS_MODIFIED_FILES}" "${STEPS_CHANGED_FILES_OUTPUTS_RENAMED_FILES}" | tr ' ' '\n')
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
done
|
||||
|
||||
if [ -n "$found_in" ]; then
|
||||
found_in="${found_in%, }"
|
||||
found_in=$(echo "$found_in" | sed 's/, $//')
|
||||
MAPPED="${MAPPED}- \`${check_id}\` (\`${provider}\`): ${found_in}"$'\n'
|
||||
else
|
||||
UNMAPPED="${UNMAPPED}- \`${check_id}\` (\`${provider}\`)"$'\n'
|
||||
|
||||
@@ -74,15 +74,15 @@ jobs:
|
||||
done <<< "$STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES"
|
||||
|
||||
if [ "$HAS_CONFLICTS" = true ]; then
|
||||
echo "has_conflicts=true" >> "$GITHUB_OUTPUT"
|
||||
echo "has_conflicts=true" >> $GITHUB_OUTPUT
|
||||
{
|
||||
echo "conflict_files<<EOF"
|
||||
echo "$CONFLICT_FILES"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
} >> $GITHUB_OUTPUT
|
||||
echo "Conflict markers detected"
|
||||
else
|
||||
echo "has_conflicts=false" >> "$GITHUB_OUTPUT"
|
||||
echo "has_conflicts=false" >> $GITHUB_OUTPUT
|
||||
echo "No conflict markers found in changed files"
|
||||
fi
|
||||
env:
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
id: vars
|
||||
run: |
|
||||
SHORT_SHA="${GITHUB_EVENT_PULL_REQUEST_MERGE_COMMIT_SHA}"
|
||||
echo "short_sha=${SHORT_SHA::7}" >> "$GITHUB_OUTPUT"
|
||||
echo "short_sha=${SHORT_SHA::7}" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
GITHUB_EVENT_PULL_REQUEST_MERGE_COMMIT_SHA: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
|
||||
@@ -46,7 +46,6 @@ jobs:
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
repository: ${{ secrets.CLOUD_DISPATCH }}
|
||||
event-type: prowler-pull-request-merged
|
||||
# repository_dispatch caps client_payload at 10 properties; this is exactly at the cap.
|
||||
client-payload: |
|
||||
{
|
||||
"PROWLER_COMMIT_SHA": "${{ github.event.pull_request.merge_commit_sha }}",
|
||||
@@ -55,8 +54,8 @@ jobs:
|
||||
"PROWLER_PR_TITLE": ${{ toJson(github.event.pull_request.title) }},
|
||||
"PROWLER_PR_LABELS": ${{ toJson(github.event.pull_request.labels.*.name) }},
|
||||
"PROWLER_PR_BODY": ${{ toJson(github.event.pull_request.body) }},
|
||||
"PROWLER_PR_URL": ${{ toJson(github.event.pull_request.html_url) }},
|
||||
"PROWLER_PR_MERGED_BY": "${{ github.event.pull_request.merged_by.login }}",
|
||||
"PROWLER_PR_STACK_NUMBER": "${{ github.event.pull_request.stack.number }}",
|
||||
"PROWLER_PR_STACK_POSITION": "${{ github.event.pull_request.stack.position }}",
|
||||
"PROWLER_PR_STACK_SIZE": "${{ github.event.pull_request.stack.size }}"
|
||||
"PROWLER_PR_BASE_BRANCH": ${{ toJson(github.event.pull_request.base.ref) }},
|
||||
"PROWLER_PR_HEAD_BRANCH": ${{ toJson(github.event.pull_request.head.ref) }}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
- name: Enable release freeze
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh variable set RELEASE_FREEZE --body true --repo "${GITHUB_REPOSITORY}"
|
||||
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
|
||||
echo "Prowler version: $PROWLER_VERSION"
|
||||
echo "Branch name: $BRANCH_NAME"
|
||||
echo "Is minor release: $([ "$PATCH_VERSION" -eq 0 ] && echo 'true' || echo 'false')"
|
||||
echo "Is minor release: $([ $PATCH_VERSION -eq 0 ] && echo 'true' || echo 'false')"
|
||||
else
|
||||
echo "Invalid version syntax: '$PROWLER_VERSION' (must be N.N.N)" >&2
|
||||
exit 1
|
||||
@@ -107,8 +107,7 @@ jobs:
|
||||
if [ -f "$changelog_file" ]; then
|
||||
# Extract version that matches this Prowler release
|
||||
# Format: ## [version] (Prowler X.Y.Z) or ## [vversion] (Prowler vX.Y.Z)
|
||||
local version
|
||||
version=$(grep '^## \[' "$changelog_file" | grep "(Prowler v\?${prowler_version})" | head -1 | sed 's/^## \[\(.*\)\].*/\1/' | sed 's/^v//' | tr -d '[:space:]')
|
||||
local version=$(grep '^## \[' "$changelog_file" | grep "(Prowler v\?${prowler_version})" | head -1 | sed 's/^## \[\(.*\)\].*/\1/' | sed 's/^v//' | tr -d '[:space:]')
|
||||
echo "$version"
|
||||
else
|
||||
echo ""
|
||||
@@ -179,55 +178,55 @@ jobs:
|
||||
|
||||
# Determine if components have changes for this specific release
|
||||
if [ -n "$SDK_VERSION" ]; then
|
||||
echo "HAS_SDK_CHANGES=true" >> "$GITHUB_ENV"
|
||||
echo "HAS_SDK_CHANGES=true" >> $GITHUB_ENV
|
||||
HAS_SDK_CHANGES="true"
|
||||
echo "✓ SDK changes detected - version: $SDK_VERSION"
|
||||
extract_changelog "prowler/CHANGELOG.md" "$SDK_VERSION" "prowler_changelog.md"
|
||||
else
|
||||
echo "HAS_SDK_CHANGES=false" >> "$GITHUB_ENV"
|
||||
echo "HAS_SDK_CHANGES=false" >> $GITHUB_ENV
|
||||
HAS_SDK_CHANGES="false"
|
||||
echo "ℹ No SDK changes for this release"
|
||||
touch "prowler_changelog.md"
|
||||
fi
|
||||
|
||||
if [ -n "$API_VERSION" ]; then
|
||||
echo "HAS_API_CHANGES=true" >> "$GITHUB_ENV"
|
||||
echo "HAS_API_CHANGES=true" >> $GITHUB_ENV
|
||||
HAS_API_CHANGES="true"
|
||||
echo "✓ API changes detected - version: $API_VERSION"
|
||||
extract_changelog "api/CHANGELOG.md" "$API_VERSION" "api_changelog.md"
|
||||
else
|
||||
echo "HAS_API_CHANGES=false" >> "$GITHUB_ENV"
|
||||
echo "HAS_API_CHANGES=false" >> $GITHUB_ENV
|
||||
HAS_API_CHANGES="false"
|
||||
echo "ℹ No API changes for this release"
|
||||
touch "api_changelog.md"
|
||||
fi
|
||||
|
||||
if [ -n "$UI_VERSION" ]; then
|
||||
echo "HAS_UI_CHANGES=true" >> "$GITHUB_ENV"
|
||||
echo "HAS_UI_CHANGES=true" >> $GITHUB_ENV
|
||||
HAS_UI_CHANGES="true"
|
||||
echo "✓ UI changes detected - version: $UI_VERSION"
|
||||
extract_changelog "ui/CHANGELOG.md" "$UI_VERSION" "ui_changelog.md"
|
||||
else
|
||||
echo "HAS_UI_CHANGES=false" >> "$GITHUB_ENV"
|
||||
echo "HAS_UI_CHANGES=false" >> $GITHUB_ENV
|
||||
HAS_UI_CHANGES="false"
|
||||
echo "ℹ No UI changes for this release"
|
||||
touch "ui_changelog.md"
|
||||
fi
|
||||
|
||||
if [ -n "$MCP_VERSION" ]; then
|
||||
echo "HAS_MCP_CHANGES=true" >> "$GITHUB_ENV"
|
||||
echo "HAS_MCP_CHANGES=true" >> $GITHUB_ENV
|
||||
HAS_MCP_CHANGES="true"
|
||||
echo "✓ MCP changes detected - version: $MCP_VERSION"
|
||||
extract_changelog "mcp_server/CHANGELOG.md" "$MCP_VERSION" "mcp_changelog.md"
|
||||
else
|
||||
echo "HAS_MCP_CHANGES=false" >> "$GITHUB_ENV"
|
||||
echo "HAS_MCP_CHANGES=false" >> $GITHUB_ENV
|
||||
HAS_MCP_CHANGES="false"
|
||||
echo "ℹ No MCP changes for this release"
|
||||
touch "mcp_changelog.md"
|
||||
fi
|
||||
|
||||
# Combine changelogs in order: UI, API, SDK, MCP
|
||||
: > combined_changelog.md
|
||||
> combined_changelog.md
|
||||
|
||||
if [ "$HAS_UI_CHANGES" = "true" ] && [ -s "ui_changelog.md" ]; then
|
||||
echo "## UI" >> combined_changelog.md
|
||||
@@ -390,4 +389,3 @@ jobs:
|
||||
if: always()
|
||||
run: |
|
||||
rm -f prowler_changelog.md api_changelog.md ui_changelog.md mcp_changelog.md combined_changelog.md
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
prowler_version: ${{ steps.get-prowler-version.outputs.prowler_version }}
|
||||
created: ${{ steps.get-prowler-version.outputs.created }}
|
||||
latest_tag: ${{ steps.get-prowler-version.outputs.latest_tag }}
|
||||
stable_tag: ${{ steps.get-prowler-version.outputs.stable_tag }}
|
||||
permissions:
|
||||
@@ -65,9 +64,9 @@ jobs:
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
files.pythonhosted.org:443
|
||||
github.com:443
|
||||
pypi.org:443
|
||||
files.pythonhosted.org:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -87,7 +86,6 @@ jobs:
|
||||
fi
|
||||
echo "latest_tag=latest" >> "${GITHUB_OUTPUT}"
|
||||
echo "stable_tag=stable" >> "${GITHUB_OUTPUT}"
|
||||
echo "created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
notify-release-started:
|
||||
if: github.repository == 'prowler-cloud/prowler' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
||||
@@ -148,24 +146,24 @@ jobs:
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
_http._tcp.deb.debian.org:443
|
||||
aka.ms:443
|
||||
api.ecr-public.us-east-1.amazonaws.com:443
|
||||
auth.docker.io:443
|
||||
cdn.powershellgallery.com:443
|
||||
debian.map.fastlydns.net:80
|
||||
files.pythonhosted.org:443
|
||||
github.com:443
|
||||
powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
public.ecr.aws:443
|
||||
pypi.org:443
|
||||
registry-1.docker.io:443
|
||||
release-assets.githubusercontent.com:443
|
||||
sts.amazonaws.com:443
|
||||
sts.us-east-1.amazonaws.com:443
|
||||
registry-1.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
auth.docker.io:443
|
||||
debian.map.fastlydns.net:80
|
||||
github.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
pypi.org:443
|
||||
files.pythonhosted.org:443
|
||||
www.powershellgallery.com:443
|
||||
aka.ms:443
|
||||
cdn.powershellgallery.com:443
|
||||
_http._tcp.deb.debian.org:443
|
||||
powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -200,20 +198,9 @@ jobs:
|
||||
context: .
|
||||
file: ${{ env.DOCKERFILE_PATH }}
|
||||
push: true
|
||||
sbom: true
|
||||
# max, not the default min: min records little beyond the build ref.
|
||||
provenance: mode=max
|
||||
platforms: ${{ matrix.platform }}
|
||||
tags: |
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.latest_tag }}-${{ matrix.arch }}
|
||||
labels: |
|
||||
org.opencontainers.image.title=Prowler CLI
|
||||
org.opencontainers.image.description=Open Source security tool for cloud security assessments, audits, incident response, continuous monitoring, hardening and forensics readiness
|
||||
org.opencontainers.image.vendor=ProwlerPro, Inc.
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.created=${{ needs.setup.outputs.created }}
|
||||
org.opencontainers.image.version=${{ needs.setup.outputs.prowler_version }}
|
||||
cache-from: type=gha,scope=${{ matrix.arch }}
|
||||
cache-to: type=gha,mode=${{ github.event_name == 'pull_request' && 'min' || 'max' }},scope=${{ matrix.arch }}
|
||||
|
||||
@@ -232,14 +219,14 @@ jobs:
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
api.ecr-public.us-east-1.amazonaws.com:443
|
||||
registry-1.docker.io:443
|
||||
auth.docker.io:443
|
||||
github.com:443
|
||||
public.ecr.aws:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
public.ecr.aws:443
|
||||
registry-1.docker.io:443
|
||||
github.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
api.ecr-public.us-east-1.amazonaws.com:443
|
||||
sts.amazonaws.com:443
|
||||
sts.us-east-1.amazonaws.com:443
|
||||
|
||||
@@ -265,10 +252,10 @@ jobs:
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_LATEST_TAG}" \
|
||||
-t "${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_LATEST_TAG}" \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_LATEST_TAG}-amd64" \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_LATEST_TAG}-arm64"
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_LATEST_TAG} \
|
||||
-t ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_LATEST_TAG} \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_LATEST_TAG}-amd64 \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_LATEST_TAG}-arm64
|
||||
env:
|
||||
NEEDS_SETUP_OUTPUTS_LATEST_TAG: ${{ needs.setup.outputs.latest_tag }}
|
||||
|
||||
@@ -276,12 +263,12 @@ jobs:
|
||||
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_PROWLER_VERSION}" \
|
||||
-t "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_STABLE_TAG}" \
|
||||
-t "${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${NEEDS_SETUP_OUTPUTS_PROWLER_VERSION}" \
|
||||
-t "${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${NEEDS_SETUP_OUTPUTS_STABLE_TAG}" \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_LATEST_TAG}-amd64" \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_LATEST_TAG}-arm64"
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_PROWLER_VERSION} \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_STABLE_TAG} \
|
||||
-t ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${NEEDS_SETUP_OUTPUTS_PROWLER_VERSION} \
|
||||
-t ${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${NEEDS_SETUP_OUTPUTS_STABLE_TAG} \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_LATEST_TAG}-amd64 \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_LATEST_TAG}-arm64
|
||||
env:
|
||||
NEEDS_SETUP_OUTPUTS_PROWLER_VERSION: ${{ needs.setup.outputs.prowler_version }}
|
||||
NEEDS_SETUP_OUTPUTS_STABLE_TAG: ${{ needs.setup.outputs.stable_tag }}
|
||||
@@ -306,7 +293,7 @@ jobs:
|
||||
if: needs.setup.outputs.latest_tag == 'latest' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t "${{ env.TONIBLYX_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_PROWLER_VERSION}" \
|
||||
-t ${{ env.TONIBLYX_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_PROWLER_VERSION} \
|
||||
-t ${{ env.TONIBLYX_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:stable \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:stable
|
||||
env:
|
||||
@@ -356,9 +343,9 @@ jobs:
|
||||
id: outcome
|
||||
run: |
|
||||
if [[ "${NEEDS_CONTAINER_BUILD_PUSH_RESULT}" == "success" && "${NEEDS_CREATE_MANIFEST_RESULT}" == "success" ]]; then
|
||||
echo "outcome=success" >> "$GITHUB_OUTPUT"
|
||||
echo "outcome=success" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "outcome=failure" >> "$GITHUB_OUTPUT"
|
||||
echo "outcome=failure" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
NEEDS_CONTAINER_BUILD_PUSH_RESULT: ${{ needs.container-build-push.result }}
|
||||
|
||||
@@ -10,7 +10,6 @@ on:
|
||||
- 'Dockerfile*'
|
||||
- 'pyproject.toml'
|
||||
- 'uv.lock'
|
||||
- '.trivyignore.yaml'
|
||||
- '.github/workflows/sdk-container-checks.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
@@ -84,10 +83,6 @@ jobs:
|
||||
api.github.com:443
|
||||
mirror.gcr.io:443
|
||||
check.trivy.dev:443
|
||||
raw.githubusercontent.com:443
|
||||
objects.githubusercontent.com:443
|
||||
grype.anchore.io:443
|
||||
get.anchore.io:443
|
||||
debian.map.fastlydns.net:80
|
||||
release-assets.githubusercontent.com:443
|
||||
objects.githubusercontent.com:443
|
||||
@@ -117,12 +112,8 @@ jobs:
|
||||
Dockerfile*
|
||||
pyproject.toml
|
||||
uv.lock
|
||||
.trivyignore.yaml
|
||||
.github/workflows/sdk-container-checks.yml
|
||||
.github/actions/trivy-scan/**
|
||||
.github/actions/grype-scan/**
|
||||
.grype.yaml
|
||||
.github/scripts/grype-pr-comment.js
|
||||
files_ignore: |
|
||||
prowler/CHANGELOG.md
|
||||
prowler/changelog.d/**
|
||||
@@ -149,13 +140,5 @@ jobs:
|
||||
with:
|
||||
image-name: ${{ env.IMAGE_NAME }}
|
||||
image-tag: ${{ github.sha }}
|
||||
fail-on-severity: 'high'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
|
||||
- name: Scan SDK container with Grype
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/grype-scan
|
||||
with:
|
||||
image-name: ${{ env.IMAGE_NAME }}
|
||||
image-tag: ${{ github.sha }}
|
||||
fail-on-severity: 'high'
|
||||
fail-on-critical: 'true'
|
||||
severity: 'CRITICAL'
|
||||
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
run: uv build
|
||||
|
||||
- name: Publish Prowler package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
with:
|
||||
print-hash: true
|
||||
|
||||
@@ -129,6 +129,6 @@ jobs:
|
||||
run: uv build
|
||||
|
||||
- name: Publish prowler-cloud package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
with:
|
||||
print-hash: true
|
||||
|
||||
@@ -216,8 +216,7 @@ jobs:
|
||||
elif [ -z "${STEPS_AWS_SERVICES_OUTPUTS_SERVICE_PATHS}" ]; then
|
||||
echo "No AWS service paths detected; skipping AWS tests."
|
||||
else
|
||||
read -ra service_paths <<< "${STEPS_AWS_SERVICES_OUTPUTS_SERVICE_PATHS}"
|
||||
uv run pytest -n auto --cov=./prowler/providers/aws --cov-report=xml:aws_coverage.xml "${service_paths[@]}"
|
||||
uv run pytest -n auto --cov=./prowler/providers/aws --cov-report=xml:aws_coverage.xml ${STEPS_AWS_SERVICES_OUTPUTS_SERVICE_PATHS}
|
||||
fi
|
||||
env:
|
||||
STEPS_AWS_SERVICES_OUTPUTS_RUN_ALL: ${{ steps.aws-services.outputs.run_all }}
|
||||
|
||||
@@ -84,8 +84,7 @@ jobs:
|
||||
echo "Changed files:"
|
||||
echo "${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}" | tr ' ' '\n'
|
||||
echo ""
|
||||
read -ra changed <<< "${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}"
|
||||
python .github/scripts/test-impact.py "${changed[@]}"
|
||||
python .github/scripts/test-impact.py ${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}
|
||||
env:
|
||||
STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
|
||||
@@ -93,21 +92,21 @@ jobs:
|
||||
id: set-flags
|
||||
run: |
|
||||
if [[ -n "${STEPS_IMPACT_OUTPUTS_SDK_TESTS}" ]]; then
|
||||
echo "has-sdk-tests=true" >> "$GITHUB_OUTPUT"
|
||||
echo "has-sdk-tests=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has-sdk-tests=false" >> "$GITHUB_OUTPUT"
|
||||
echo "has-sdk-tests=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
if [[ -n "${STEPS_IMPACT_OUTPUTS_API_TESTS}" ]]; then
|
||||
echo "has-api-tests=true" >> "$GITHUB_OUTPUT"
|
||||
echo "has-api-tests=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has-api-tests=false" >> "$GITHUB_OUTPUT"
|
||||
echo "has-api-tests=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
if [[ -n "${STEPS_IMPACT_OUTPUTS_UI_E2E}" ]]; then
|
||||
echo "has-ui-e2e=true" >> "$GITHUB_OUTPUT"
|
||||
echo "has-ui-e2e=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has-ui-e2e=false" >> "$GITHUB_OUTPUT"
|
||||
echo "has-ui-e2e=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
STEPS_IMPACT_OUTPUTS_SDK_TESTS: ${{ steps.impact.outputs.sdk-tests }}
|
||||
@@ -116,22 +115,22 @@ jobs:
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "## Test Impact Analysis" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "## Test Impact Analysis" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [[ "${STEPS_IMPACT_OUTPUTS_RUN_ALL}" == "true" ]]; then
|
||||
echo "🚨 **Critical path changed - running ALL tests**" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "🚨 **Critical path changed - running ALL tests**" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "### Affected Modules" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "\`${STEPS_IMPACT_OUTPUTS_MODULES}\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "### Affected Modules" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`${STEPS_IMPACT_OUTPUTS_MODULES}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
echo "### Tests to Run" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| Category | Paths |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "|----------|-------|" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| SDK Tests | \`${STEPS_IMPACT_OUTPUTS_SDK_TESTS:-none}\` |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| API Tests | \`${STEPS_IMPACT_OUTPUTS_API_TESTS:-none}\` |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "| UI E2E | \`${STEPS_IMPACT_OUTPUTS_UI_E2E:-none}\` |" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "### Tests to Run" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Category | Paths |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| SDK Tests | \`${STEPS_IMPACT_OUTPUTS_SDK_TESTS:-none}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| API Tests | \`${STEPS_IMPACT_OUTPUTS_API_TESTS:-none}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| UI E2E | \`${STEPS_IMPACT_OUTPUTS_UI_E2E:-none}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
env:
|
||||
|
||||
@@ -41,20 +41,17 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
short-sha: ${{ steps.set-short-sha.outputs.short-sha }}
|
||||
created: ${{ steps.set-short-sha.outputs.created }}
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
||||
with:
|
||||
egress-policy: block
|
||||
egress-policy: audit
|
||||
|
||||
- name: Calculate short SHA
|
||||
id: set-short-sha
|
||||
run: |
|
||||
echo "short-sha=${GITHUB_SHA::7}" >> "${GITHUB_OUTPUT}"
|
||||
echo "created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "${GITHUB_OUTPUT}"
|
||||
run: echo "short-sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
|
||||
|
||||
notify-release-started:
|
||||
if: github.repository == 'prowler-cloud/prowler' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
||||
@@ -114,15 +111,15 @@ jobs:
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
registry-1.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
auth.docker.io:443
|
||||
registry.npmjs.org:443
|
||||
dl-cdn.alpinelinux.org:443
|
||||
fonts.googleapis.com:443
|
||||
fonts.gstatic.com:443
|
||||
github.com:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
registry-1.docker.io:443
|
||||
registry.npmjs.org:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -147,20 +144,9 @@ jobs:
|
||||
build-args: |
|
||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=${{ (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && format('v{0}', env.RELEASE_TAG) || needs.setup.outputs.short-sha }}
|
||||
push: true
|
||||
sbom: true
|
||||
# max, not the default min: min records little beyond the build ref.
|
||||
provenance: mode=max
|
||||
platforms: ${{ matrix.platform }}
|
||||
tags: |
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ needs.setup.outputs.short-sha }}-${{ matrix.arch }}
|
||||
labels: |
|
||||
org.opencontainers.image.title=Prowler Local Server UI
|
||||
org.opencontainers.image.description=Web UI for Prowler Local Server (Next.js)
|
||||
org.opencontainers.image.vendor=ProwlerPro, Inc.
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.created=${{ needs.setup.outputs.created }}
|
||||
${{ (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && format('org.opencontainers.image.version={0}', env.RELEASE_TAG) || '' }}
|
||||
cache-from: type=gha,scope=${{ matrix.arch }}
|
||||
cache-to: type=gha,mode=${{ github.event_name == 'pull_request' && 'min' || 'max' }},scope=${{ matrix.arch }}
|
||||
|
||||
@@ -178,12 +164,12 @@ jobs:
|
||||
with:
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
auth.docker.io:443
|
||||
github.com:443
|
||||
release-assets.githubusercontent.com:443
|
||||
registry-1.docker.io:443
|
||||
auth.docker.io:443
|
||||
production.cloudflare.docker.com:443
|
||||
production.cloudfront.docker.com:443
|
||||
registry-1.docker.io:443
|
||||
release-assets.githubusercontent.com:443
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
@@ -196,9 +182,9 @@ jobs:
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.LATEST_TAG }} \
|
||||
-t "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}" \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-amd64" \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-arm64"
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA} \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-amd64 \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-arm64
|
||||
env:
|
||||
NEEDS_SETUP_OUTPUTS_SHORT_SHA: ${{ needs.setup.outputs.short-sha }}
|
||||
|
||||
@@ -206,10 +192,10 @@ jobs:
|
||||
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t "${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${RELEASE_TAG}" \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${RELEASE_TAG} \
|
||||
-t ${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${{ env.STABLE_TAG }} \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-amd64" \
|
||||
"${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-arm64"
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-amd64 \
|
||||
${{ env.PROWLERCLOUD_DOCKERHUB_REPOSITORY }}/${{ env.PROWLERCLOUD_DOCKERHUB_IMAGE }}:${NEEDS_SETUP_OUTPUTS_SHORT_SHA}-arm64
|
||||
env:
|
||||
NEEDS_SETUP_OUTPUTS_SHORT_SHA: ${{ needs.setup.outputs.short-sha }}
|
||||
|
||||
@@ -249,9 +235,9 @@ jobs:
|
||||
id: outcome
|
||||
run: |
|
||||
if [[ "${NEEDS_CONTAINER_BUILD_PUSH_RESULT}" == "success" && "${NEEDS_CREATE_MANIFEST_RESULT}" == "success" ]]; then
|
||||
echo "outcome=success" >> "$GITHUB_OUTPUT"
|
||||
echo "outcome=success" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "outcome=failure" >> "$GITHUB_OUTPUT"
|
||||
echo "outcome=failure" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
NEEDS_CONTAINER_BUILD_PUSH_RESULT: ${{ needs.container-build-push.result }}
|
||||
|
||||
@@ -88,9 +88,6 @@ jobs:
|
||||
get.trivy.dev:443
|
||||
release-assets.githubusercontent.com:443
|
||||
objects.githubusercontent.com:443
|
||||
raw.githubusercontent.com:443
|
||||
grype.anchore.io:443
|
||||
get.anchore.io:443
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -104,11 +101,7 @@ jobs:
|
||||
with:
|
||||
files: |
|
||||
ui/**
|
||||
.trivyignore.yaml
|
||||
.github/actions/trivy-scan/**
|
||||
.github/actions/grype-scan/**
|
||||
.grype.yaml
|
||||
.github/scripts/grype-pr-comment.js
|
||||
files_ignore: |
|
||||
ui/CHANGELOG.md
|
||||
ui/changelog.d/**
|
||||
@@ -139,13 +132,5 @@ jobs:
|
||||
with:
|
||||
image-name: ${{ env.IMAGE_NAME }}
|
||||
image-tag: ${{ github.sha }}
|
||||
fail-on-severity: 'high'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
|
||||
- name: Scan UI container with Grype
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/grype-scan
|
||||
with:
|
||||
image-name: ${{ env.IMAGE_NAME }}
|
||||
image-tag: ${{ github.sha }}
|
||||
fail-on-severity: 'high'
|
||||
fail-on-critical: 'true'
|
||||
severity: 'CRITICAL'
|
||||
|
||||
@@ -108,14 +108,14 @@ jobs:
|
||||
|
||||
- name: Show test scope
|
||||
run: |
|
||||
echo "## E2E Test Scope" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "## E2E Test Scope" >> $GITHUB_STEP_SUMMARY
|
||||
if [[ "${RUN_ALL_TESTS}" == "true" ]]; then
|
||||
echo "Running **ALL** E2E tests (critical path changed)" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "Running **ALL** E2E tests (critical path changed)" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "Running tests matching: \`${E2E_TEST_PATHS}\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "Running tests matching: \`${E2E_TEST_PATHS}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo ""
|
||||
echo "Affected modules: \`${NEEDS_IMPACT_ANALYSIS_OUTPUTS_MODULES}\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "Affected modules: \`${NEEDS_IMPACT_ANALYSIS_OUTPUTS_MODULES}\`" >> $GITHUB_STEP_SUMMARY
|
||||
env:
|
||||
NEEDS_IMPACT_ANALYSIS_OUTPUTS_MODULES: ${{ needs.impact-analysis.outputs.modules }}
|
||||
|
||||
@@ -233,7 +233,7 @@ jobs:
|
||||
yq -i '.services.worker.networks = ["kind","default"]' docker-compose.yml
|
||||
|
||||
- name: Fix API data directory permissions
|
||||
run: docker run --rm -v "$(pwd)/_data/api:/data" alpine chown -R 1000:1000 /data
|
||||
run: docker run --rm -v $(pwd)/_data/api:/data alpine chown -R 1000:1000 /data
|
||||
|
||||
- name: Add AWS credentials for testing
|
||||
run: |
|
||||
@@ -267,7 +267,7 @@ jobs:
|
||||
timeout=150
|
||||
elapsed=0
|
||||
while [ $elapsed -lt $timeout ]; do
|
||||
if curl -s "${UI_API_BASE_URL}/docs" >/dev/null 2>&1; then
|
||||
if curl -s ${UI_API_BASE_URL}/docs >/dev/null 2>&1; then
|
||||
echo "Prowler API is ready!"
|
||||
exit 0
|
||||
fi
|
||||
@@ -301,7 +301,7 @@ jobs:
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm and Next.js cache
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
@@ -387,8 +387,7 @@ jobs:
|
||||
fi
|
||||
TEST_PATHS=$(echo "$VALID_PATHS" | tr '\n' ' ')
|
||||
echo "Resolved test paths: $TEST_PATHS"
|
||||
read -ra test_paths <<< "$TEST_PATHS"
|
||||
pnpm exec playwright test "${test_paths[@]}"
|
||||
pnpm exec playwright test $TEST_PATHS
|
||||
fi
|
||||
|
||||
- name: Upload test reports
|
||||
@@ -445,12 +444,12 @@ jobs:
|
||||
|
||||
- name: No E2E tests needed
|
||||
run: |
|
||||
echo "## E2E Tests Skipped" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "No UI E2E tests needed for this change." >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "Affected modules: \`${NEEDS_IMPACT_ANALYSIS_OUTPUTS_MODULES}\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "To run all tests, modify a file in a critical path (e.g., \`ui/lib/**\`)." >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "## E2E Tests Skipped" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No UI E2E tests needed for this change." >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Affected modules: \`${NEEDS_IMPACT_ANALYSIS_OUTPUTS_MODULES}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "To run all tests, modify a file in a critical path (e.g., \`ui/lib/**\`)." >> $GITHUB_STEP_SUMMARY
|
||||
env:
|
||||
NEEDS_IMPACT_ANALYSIS_OUTPUTS_MODULES: ${{ needs.impact-analysis.outputs.modules }}
|
||||
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
- name: Get pnpm store directory
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
shell: bash
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm and Next.js cache
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
@@ -157,8 +157,7 @@ jobs:
|
||||
echo "${STEPS_CHANGED_SOURCE_OUTPUTS_ALL_CHANGED_FILES}"
|
||||
# Convert space-separated to vitest related format (remove ui/ prefix for relative paths)
|
||||
CHANGED_FILES=$(echo "${STEPS_CHANGED_SOURCE_OUTPUTS_ALL_CHANGED_FILES}" | tr ' ' '\n' | sed 's|^ui/||' | tr '\n' ' ')
|
||||
read -ra changed <<< "$CHANGED_FILES"
|
||||
pnpm exec vitest related "${changed[@]}" --run --project unit
|
||||
pnpm exec vitest related $CHANGED_FILES --run --project unit
|
||||
env:
|
||||
STEPS_CHANGED_SOURCE_OUTPUTS_ALL_CHANGED_FILES: ${{ steps.changed-source.outputs.all_changed_files }}
|
||||
|
||||
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
# Findings excluded from the Grype gate, each with a reason.
|
||||
# Anything not listed here blocks the pull request at critical or high severity.
|
||||
# Pairs are explicit: a new CVE against an already-listed package still blocks.
|
||||
#
|
||||
# Every entry below has a published fix we cannot take. Findings with no fix at all are
|
||||
# not listed: the scan runs with only-fixed, so they never reach the gate.
|
||||
|
||||
ignore:
|
||||
|
||||
# Modules compiled into the Trivy binary we ship.
|
||||
# Only a Trivy rebuild by its vendor can change these; the version is pinned in our Dockerfile.
|
||||
# CVE-2026-71556 is the same temporary exception documented in .trivyignore.yaml:
|
||||
# Trivy 0.73.0 still embeds go-git 5.19.1, while the 5.19.2 fix is merged only on
|
||||
# Trivy main. Remove this entry with the Trivy exception by 2026-09-15.
|
||||
# https://github.com/aquasecurity/trivy/blob/v0.73.0/go.mod#L46
|
||||
# https://github.com/aquasecurity/trivy/commit/a2edba9a03987ba0d2ebc8212c1a9a1e6979497b
|
||||
- vulnerability: CVE-2026-71556
|
||||
package:
|
||||
name: github.com/go-git/go-git/v5
|
||||
- vulnerability: CVE-2026-56852
|
||||
package:
|
||||
name: golang.org/x/text
|
||||
- vulnerability: GHSA-hrxh-6v49-42gf
|
||||
package:
|
||||
name: google.golang.org/grpc
|
||||
- vulnerability: CVE-2026-50151
|
||||
package:
|
||||
name: oras.land/oras-go/v2
|
||||
|
||||
# Shipped inside the PowerShell tarball, in its bundled MicrosoftTeams module.
|
||||
# Not a dependency we declare, and not one we can upgrade independently.
|
||||
- vulnerability: CVE-2026-26127
|
||||
package:
|
||||
name: Microsoft.Bcl.Memory
|
||||
|
||||
# The .NET runtime bundled inside the PowerShell tarball the Dockerfile pins.
|
||||
# CVE-2026-62901 is the same temporary exception documented in .trivyignore.yaml:
|
||||
# fixed in .NET 9.0.19 / 10.0.11 (2026-08-11), but no published PowerShell release
|
||||
# ships a patched runtime yet (7.5.9 bundles 9.0.18; 7.6.4 bundles 10.0.x < 10.0.11).
|
||||
# pwsh runs only local M365 module cmdlets; nothing listens for inbound WebSocket
|
||||
# connections. Remove with the Trivy exception by 2026-09-15.
|
||||
- vulnerability: CVE-2026-62901
|
||||
package:
|
||||
name: Microsoft.NETCore.App.Runtime.linux-x64
|
||||
- vulnerability: CVE-2026-62901
|
||||
package:
|
||||
name: Microsoft.NETCore.App.Runtime.linux-arm64
|
||||
|
||||
|
||||
# The CPython interpreter, compiled into the official base image.
|
||||
# TEMPORARY, unlike the entries above: moving to Python 3.13 clears seven of these, and
|
||||
# that is a runtime upgrade pending its own evaluation. The remaining three need 3.15 and
|
||||
# are unfixable either way -- the MCP image already runs 3.13.14 and still reports them.
|
||||
- vulnerability: CVE-2026-11940
|
||||
package:
|
||||
name: python
|
||||
- vulnerability: CVE-2026-11972
|
||||
package:
|
||||
name: python
|
||||
- vulnerability: CVE-2026-15308
|
||||
package:
|
||||
name: python
|
||||
- vulnerability: CVE-2026-3298
|
||||
package:
|
||||
name: python
|
||||
- vulnerability: CVE-2026-3644
|
||||
package:
|
||||
name: python
|
||||
- vulnerability: CVE-2026-4224
|
||||
package:
|
||||
name: python
|
||||
- vulnerability: CVE-2026-4786
|
||||
package:
|
||||
name: python
|
||||
- vulnerability: CVE-2026-6100
|
||||
package:
|
||||
name: python
|
||||
- vulnerability: CVE-2026-7210
|
||||
package:
|
||||
name: python
|
||||
- vulnerability: CVE-2026-9669
|
||||
package:
|
||||
name: python
|
||||
@@ -47,14 +47,6 @@ repos:
|
||||
priority: 20
|
||||
|
||||
## GITHUB ACTIONS
|
||||
- repo: https://github.com/rhysd/actionlint
|
||||
rev: v1.7.12
|
||||
hooks:
|
||||
- id: actionlint
|
||||
# SC2129 only suggests grouping consecutive redirects; not worth restructuring for.
|
||||
args: ['-shellcheck=-e SC2129']
|
||||
priority: 30
|
||||
|
||||
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
||||
rev: v1.24.1
|
||||
hooks:
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
# Trivy ignore file for prowlercloud/prowler SDK container image.
|
||||
# Each entry below documents (a) the affected package and why it ships in the
|
||||
# image, (b) why the CVE is not exploitable in Prowler's runtime, and (c) the
|
||||
# upstream fix status. Entries carry an expiry so they auto-force re-review.
|
||||
# Entries are scoped per-package so suppressions cannot drift onto unrelated
|
||||
# packages that may be assigned the same CVE in the future.
|
||||
#
|
||||
# Scanned by: .github/actions/trivy-scan via .github/workflows/sdk-container-checks.yml
|
||||
|
||||
# CVE-2026-42496 — perl-archive-tar path traversal via crafted symlinks.
|
||||
# CVE-2026-8376 — perl heap buffer overflow when compiling regex.
|
||||
# Packages: perl, perl-base, perl-modules-5.36, libperl5.36.
|
||||
# Why ignored: perl-base is part of Debian's "Essential: yes" set; it cannot be
|
||||
# removed without breaking dpkg. The Prowler SDK does not invoke perl at runtime;
|
||||
# neither vulnerable code path (Archive::Tar parsing or regex compilation of
|
||||
# attacker-controlled input) is reachable from Prowler. No Debian bookworm fix
|
||||
# is available yet.
|
||||
CVE-2026-42496 pkg:perl exp:2026-08-15
|
||||
CVE-2026-42496 pkg:perl-base exp:2026-08-15
|
||||
CVE-2026-42496 pkg:perl-modules-5.36 exp:2026-08-15
|
||||
CVE-2026-42496 pkg:libperl5.36 exp:2026-08-15
|
||||
CVE-2026-8376 pkg:perl exp:2026-08-15
|
||||
CVE-2026-8376 pkg:perl-base exp:2026-08-15
|
||||
CVE-2026-8376 pkg:perl-modules-5.36 exp:2026-08-15
|
||||
CVE-2026-8376 pkg:libperl5.36 exp:2026-08-15
|
||||
|
||||
# CVE-2026-13221 - Perl regex trie overflow.
|
||||
# Packages: perl, perl-base, perl-modules-5.36, libperl5.36.
|
||||
# Why ignored: upstream confirms Perl 5.36.0 is not affected; the regression
|
||||
# was introduced after this version. Debian currently marks bookworm as
|
||||
# vulnerable, which causes Trivy to report a false positive.
|
||||
# Ref: https://github.com/Perl/perl5/issues/23388
|
||||
CVE-2026-13221 pkg:perl exp:2026-08-15
|
||||
CVE-2026-13221 pkg:perl-base exp:2026-08-15
|
||||
CVE-2026-13221 pkg:perl-modules-5.36 exp:2026-08-15
|
||||
CVE-2026-13221 pkg:libperl5.36 exp:2026-08-15
|
||||
|
||||
# CVE-2026-57433 — Perl Storable signed integer overflow when deserializing a
|
||||
# crafted SX_HOOK record (retrieve_hook_common passes a wrapped negative count
|
||||
# to av_extend).
|
||||
# Packages: perl, perl-base, perl-modules-5.36, libperl5.36.
|
||||
# Why ignored: perl-base is part of Debian's "Essential: yes" set; it cannot be
|
||||
# removed without breaking dpkg. Prowler does not invoke perl at runtime and
|
||||
# never calls Storable's thaw/retrieve on attacker-controlled blobs, so the
|
||||
# vulnerable deserialization path is unreachable. Fixed upstream in
|
||||
# Storable 3.41; no Debian bookworm fix is available yet.
|
||||
CVE-2026-57433 pkg:perl exp:2026-08-15
|
||||
CVE-2026-57433 pkg:perl-base exp:2026-08-15
|
||||
CVE-2026-57433 pkg:perl-modules-5.36 exp:2026-08-15
|
||||
CVE-2026-57433 pkg:libperl5.36 exp:2026-08-15
|
||||
|
||||
# CVE-2025-7458 — SQLite integer overflow.
|
||||
# Package: libsqlite3-0.
|
||||
# Why ignored: transitive dependency of CPython's stdlib sqlite3 module. The
|
||||
# Prowler SDK does not open user-supplied SQLite databases; SQLite usage is
|
||||
# internal and bounded. No Debian bookworm fix is available.
|
||||
CVE-2025-7458 pkg:libsqlite3-0 exp:2026-08-15
|
||||
|
||||
# CVE-2026-43185 — Linux kernel ksmbd signedness bug.
|
||||
# Package: linux-libc-dev.
|
||||
# Why ignored: linux-libc-dev ships kernel headers for build-time compilation,
|
||||
# not a running kernel. Containers execute against the host kernel, so these
|
||||
# headers are inert at runtime. The upstream fix landed in kernel 7.0-rc2 and
|
||||
# has not been backported to Debian's 6.1 LTS line.
|
||||
CVE-2026-43185 pkg:linux-libc-dev exp:2026-08-15
|
||||
|
||||
# CVE-2023-45853 — zlib MiniZip integer overflow / heap overflow in
|
||||
# zipOpenNewFileInZip4_64.
|
||||
# Packages: zlib1g, zlib1g-dev.
|
||||
# Why ignored: Debian Security Tracker status for bookworm is <ignored>, with
|
||||
# the published rationale "contrib/minizip not built and src:zlib not producing
|
||||
# binary packages" — i.e. the vulnerable symbol is not present in the libz.so
|
||||
# shipped by Debian. Real-not-affected, not unpatched. Upstream fix is in
|
||||
# zlib 1.3.1, available in Debian trixie (13); migrating the base image would
|
||||
# clear it fully.
|
||||
# Ref: https://security-tracker.debian.org/tracker/CVE-2023-45853
|
||||
CVE-2023-45853 pkg:zlib1g exp:2026-08-15
|
||||
CVE-2023-45853 pkg:zlib1g-dev exp:2026-08-15
|
||||
|
||||
# CVE-2026-55200 — libssh2 out-of-bounds write in ssh2_transport_read() due to
|
||||
# an unchecked packet_length field in transport.c (heap corruption, possible RCE).
|
||||
# Package: libssh2-1.
|
||||
# Why ignored: libssh2-1 is pulled in only as a transitive dependency of libcurl4
|
||||
# (installed in the SDK Dockerfile for the networking/PowerShell stack). The
|
||||
# vulnerable path is reached exclusively when libssh2 acts as an SSH/SCP/SFTP
|
||||
# client parsing transport packets from a server. Prowler never uses libcurl's
|
||||
# SSH/SCP/SFTP transports; it talks to cloud provider HTTPS endpoints only, so the
|
||||
# affected code is unreachable at runtime. Fixed upstream in libssh2 commit
|
||||
# 97acf3df (PR #2052); no Debian bookworm fix is available yet.
|
||||
# Ref: https://security-tracker.debian.org/tracker/CVE-2026-55200
|
||||
CVE-2026-55200 pkg:libssh2-1 exp:2026-08-15
|
||||
|
||||
# --- API container image (api/Dockerfile) ---
|
||||
# The entries below are specific to the Prowler API image, which ships
|
||||
# PowerShell and additional build tooling on top of the same bookworm base.
|
||||
|
||||
# CVE-2026-7210 — CPython/Expat hash-flooding denial of service in
|
||||
# `xml.parsers.expat` and `xml.etree.ElementTree`.
|
||||
# Packages: the Debian system Python 3.11 (python3.11*, libpython3.11*).
|
||||
# Why ignored: the API runs under the Python 3.12 interpreter shipped in its
|
||||
# `.venv`; the system `python3.11` is only present because `python3-dev` is
|
||||
# pulled in to compile native extensions (xmlsec, lxml) and is never executed
|
||||
# at runtime. The vulnerable path requires parsing attacker-controlled XML with
|
||||
# the affected interpreter, which Prowler does not do with the system Python.
|
||||
# Full mitigation also needs libexpat >= 2.8.0; no Debian bookworm fix yet.
|
||||
CVE-2026-7210 pkg:python3.11 exp:2026-08-15
|
||||
CVE-2026-7210 pkg:python3.11-dev exp:2026-08-15
|
||||
CVE-2026-7210 pkg:python3.11-minimal exp:2026-08-15
|
||||
CVE-2026-7210 pkg:libpython3.11 exp:2026-08-15
|
||||
CVE-2026-7210 pkg:libpython3.11-dev exp:2026-08-15
|
||||
CVE-2026-7210 pkg:libpython3.11-minimal exp:2026-08-15
|
||||
CVE-2026-7210 pkg:libpython3.11-stdlib exp:2026-08-15
|
||||
|
||||
# CVE-2026-33278 — Unbound DNSSEC validator use-after-free (DoS, possible RCE).
|
||||
# CVE-2026-42960 — Unbound DNS cache poisoning via promiscuous additional records.
|
||||
# Package: libunbound8.
|
||||
# Why ignored: libunbound8 is a transitive apt dependency of the TLS/networking
|
||||
# stack (GnuTLS DANE support); only the shared library ships in the image. Both
|
||||
# vulnerabilities require operating a live Unbound recursive DNSSEC validator
|
||||
# that processes attacker-influenced DNS responses. Prowler never starts an
|
||||
# Unbound resolver, so neither code path is reachable. No Debian bookworm fix yet.
|
||||
CVE-2026-33278 pkg:libunbound8 exp:2026-08-15
|
||||
CVE-2026-42960 pkg:libunbound8 exp:2026-08-15
|
||||
@@ -1,170 +0,0 @@
|
||||
# Trivy suppressions for the prowlercloud/prowler SDK and API container images.
|
||||
#
|
||||
# This file replaces the classic .trivyignore, which parsed only the CVE id: the
|
||||
# `pkg:` selector written on each line was documentation and the entry suppressed
|
||||
# its CVE across every package in the image. The `purls` field below is honoured,
|
||||
# so each entry is scoped to the package it names. Verified against Trivy 0.71.2:
|
||||
# an entry given the wrong purl leaves the finding reported, where the classic
|
||||
# format suppressed it.
|
||||
#
|
||||
# `expired_at` forces re-review. Keep the dates staggered.
|
||||
#
|
||||
# The four entries below are currently redundant: the scan runs with ignore-unfixed,
|
||||
# and none of them has a published fix, so they never reach the gate either way. They
|
||||
# are kept because the reasoning is what justifies accepting them, and because they
|
||||
# apply again the moment any of them gains a fix we do not take.
|
||||
#
|
||||
# perl-base is Debian "Essential: yes". Trivy spreads src:perl CVEs across every
|
||||
# binary package built from that source, so perl-base is flagged for modules only
|
||||
# perl-modules-* ships. Neither image installs those, and nothing in either
|
||||
# invokes perl.
|
||||
#
|
||||
# Why these four are accepted rather than fixed (reviewed 2026-07-31):
|
||||
#
|
||||
# 1. No fix exists. All four report no fixed version on perl-base 5.40.1-6.
|
||||
# Debian marks CVE-2026-42496 "fix_deferred" and the other three "affected".
|
||||
# A newer base image, apt upgrade, or a newer Debian release changes nothing.
|
||||
# 2. The package cannot be removed. "Essential: yes" means removal needs
|
||||
# dpkg --force-remove-essential, which breaks apt for anything built
|
||||
# downstream from these images.
|
||||
# 3. Changing base distribution was evaluated and rejected. Alpine drops perl
|
||||
# entirely, but PowerShell publishes no linux-musl-arm64 build in any
|
||||
# release, so M365 scanning would break on arm64 -- which is what we run in
|
||||
# production. Wolfi keeps glibc and drops perl, but pinnable versioned tags
|
||||
# are a paid tier, so builds would not be reproducibly pinnable.
|
||||
#
|
||||
# Not-invoked claim verified by sweeping both images for files with a perl
|
||||
# shebang, shell/python callers of perl, ELF binaries containing "perl", and
|
||||
# .pl/.pm files or perl subprocess calls anywhere in site-packages. The only
|
||||
# consumers found are dpkg/debconf/adduser/pam tooling, none of which runs at
|
||||
# runtime, plus one build-time script inside the ExchangeOnlineManagement
|
||||
# PowerShell module that is never invoked.
|
||||
|
||||
vulnerabilities:
|
||||
# Archive::Tar path traversal. Not installed: `perl -MArchive::Tar -e1` cannot locate it.
|
||||
- id: CVE-2026-42496
|
||||
purls:
|
||||
- "pkg:deb/debian/perl-base"
|
||||
expired_at: 2027-01-31
|
||||
|
||||
# Storable integer overflow. Not installed: `perl -MStorable -e1` cannot locate it.
|
||||
- id: CVE-2026-57433
|
||||
purls:
|
||||
- "pkg:deb/debian/perl-base"
|
||||
expired_at: 2027-01-31
|
||||
|
||||
# Regex heap overflow on 32-bit builds only; both published arches are 64-bit.
|
||||
- id: CVE-2026-8376
|
||||
purls:
|
||||
- "pkg:deb/debian/perl-base"
|
||||
expired_at: 2027-01-31
|
||||
|
||||
# Regex trie bug giving silently wrong matches above 65535 alternation branches.
|
||||
# perl 5.40.1 is in range, so this rests on nothing invoking perl. Short expiry
|
||||
# to force a re-look. Ref: https://github.com/Perl/perl5/issues/23388
|
||||
- id: CVE-2026-13221
|
||||
purls:
|
||||
- "pkg:deb/debian/perl-base"
|
||||
expired_at: 2026-11-30
|
||||
|
||||
# Declared in the SPDX manifest that ships inside PowerShell's MicrosoftTeams module
|
||||
# (Modules/MicrosoftTeams/7.9.0/_manifest/spdx_2.2/manifest.spdx.json). Trivy reads that
|
||||
# SBOM and reports what it declares, which is not the same as what the image contains:
|
||||
# there is no Node runtime and no node_modules anywhere in the image, and the .NET
|
||||
# assemblies target net472, a Windows-only framework. Nothing here is reachable, and none
|
||||
# of it is a dependency we declare -- only Microsoft can change the module's contents.
|
||||
- id: CVE-2020-0606
|
||||
purls:
|
||||
- "pkg:nuget/Microsoft.WindowsDesktop.App.Ref"
|
||||
expired_at: 2027-01-31
|
||||
- id: CVE-2019-0820
|
||||
purls:
|
||||
- "pkg:nuget/System.Text.RegularExpressions"
|
||||
expired_at: 2027-01-31
|
||||
- id: CVE-2026-47302
|
||||
purls:
|
||||
- "pkg:nuget/System.Security.Cryptography.Xml"
|
||||
expired_at: 2027-01-31
|
||||
- id: CVE-2026-47304
|
||||
purls:
|
||||
- "pkg:nuget/System.Security.Cryptography.Xml"
|
||||
expired_at: 2027-01-31
|
||||
- id: CVE-2026-50525
|
||||
purls:
|
||||
- "pkg:nuget/System.Security.Cryptography.Xml"
|
||||
expired_at: 2027-01-31
|
||||
- id: CVE-2026-50527
|
||||
purls:
|
||||
- "pkg:nuget/System.Security.Cryptography.Xml"
|
||||
expired_at: 2027-01-31
|
||||
- id: CVE-2026-50648
|
||||
purls:
|
||||
- "pkg:nuget/System.Security.Cryptography.Xml"
|
||||
expired_at: 2027-01-31
|
||||
- id: CVE-2026-13676
|
||||
purls:
|
||||
- "pkg:npm/fast-uri"
|
||||
expired_at: 2027-01-31
|
||||
- id: CVE-2026-16221
|
||||
purls:
|
||||
- "pkg:npm/fast-uri"
|
||||
expired_at: 2027-01-31
|
||||
- id: CVE-2026-18446
|
||||
purls:
|
||||
- "pkg:npm/fast-uri"
|
||||
expired_at: 2027-01-31
|
||||
- id: CVE-2026-69192
|
||||
purls:
|
||||
- "pkg:npm/ip-address"
|
||||
expired_at: 2027-01-31
|
||||
|
||||
# CVE-2026-62901 is a DoS in System.Net.WebSockets (unchecked input for loop condition,
|
||||
# CWE-606), fixed in .NET 9.0.19 / 10.0.11 (published 2026-08-11). The vulnerable runtime
|
||||
# ships inside the PowerShell tarball the Dockerfile pins: 7.5.9 is the latest 7.5.x and
|
||||
# bundles .NET 9.0.18; 7.6.4 bundles .NET 10.0.x < 10.0.11, so no published PowerShell
|
||||
# release contains the fix yet. Prowler only invokes pwsh locally to run M365 module
|
||||
# cmdlets; the image does not accept inbound WebSocket connections, so the DoS path is
|
||||
# not reachable from the network. Remove this temporary suppression as soon as a
|
||||
# PowerShell release shipping .NET 9.0.19+ is available.
|
||||
- id: CVE-2026-62901
|
||||
purls:
|
||||
- "pkg:nuget/Microsoft.NETCore.App.Runtime.linux-x64"
|
||||
- "pkg:nuget/Microsoft.NETCore.App.Runtime.linux-arm64"
|
||||
expired_at: 2026-09-15
|
||||
|
||||
# Modules compiled into the Trivy binary the images ship. The binary is pinned by version
|
||||
# and verified by checksum in the Dockerfile; only a rebuild by its vendor moves these.
|
||||
# CVE-2026-71556 affects go-git worktree operations that can follow symlinks outside a
|
||||
# cloned repository. Trivy 0.73.0, the latest published release and the version the
|
||||
# images ship, still pins that vulnerable version:
|
||||
# https://github.com/aquasecurity/trivy/blob/v0.73.0/go.mod#L46
|
||||
# Trivy main already contains the 5.19.2 fix, but no published release includes it yet:
|
||||
# https://github.com/aquasecurity/trivy/commit/a2edba9a03987ba0d2ebc8212c1a9a1e6979497b
|
||||
# Prowler invokes Trivy only with `fs` on an existing local path or with `image`; it does
|
||||
# not ask Trivy to clone or mutate a Git worktree, so the affected path is not reachable.
|
||||
# Remove this temporary suppression as soon as a fixed Trivy release is available.
|
||||
- id: CVE-2026-71556
|
||||
purls:
|
||||
- "pkg:golang/github.com/go-git/go-git/v5"
|
||||
expired_at: 2026-09-15
|
||||
|
||||
- id: CVE-2026-56852
|
||||
purls:
|
||||
- "pkg:golang/golang.org/x/text"
|
||||
expired_at: 2026-12-31
|
||||
- id: GHSA-hrxh-6v49-42gf
|
||||
purls:
|
||||
- "pkg:golang/google.golang.org/grpc"
|
||||
expired_at: 2026-12-31
|
||||
- id: CVE-2026-50151
|
||||
purls:
|
||||
- "pkg:golang/oras.land/oras-go/v2"
|
||||
expired_at: 2026-12-31
|
||||
- id: CVE-2026-50163
|
||||
purls:
|
||||
- "pkg:golang/oras.land/oras-go/v2"
|
||||
expired_at: 2026-12-31
|
||||
- id: CVE-2026-39822
|
||||
purls:
|
||||
- "pkg:golang/stdlib"
|
||||
expired_at: 2026-12-31
|
||||
+5
-35
@@ -1,30 +1,22 @@
|
||||
FROM python:3.12.13-slim-trixie@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de AS build
|
||||
FROM python:3.12.13-slim-bookworm@sha256:8a7e7cc04fd3e2bd787f7f24e22d5d119aa590d429b50c95dfe12b3abe52f48b AS build
|
||||
|
||||
LABEL maintainer="https://github.com/prowler-cloud/prowler"
|
||||
LABEL org.opencontainers.image.source="https://github.com/prowler-cloud/prowler"
|
||||
|
||||
ARG POWERSHELL_VERSION=7.5.9
|
||||
ARG POWERSHELL_VERSION=7.5.0
|
||||
ENV POWERSHELL_VERSION=${POWERSHELL_VERSION}
|
||||
# Opt out of PowerShell telemetry (Application Insights -> dc.services.visualstudio.com)
|
||||
ENV POWERSHELL_TELEMETRY_OPTOUT=1
|
||||
|
||||
ARG TRIVY_VERSION=0.73.0
|
||||
ARG TRIVY_VERSION=0.71.2
|
||||
ENV TRIVY_VERSION=${TRIVY_VERSION}
|
||||
|
||||
ARG ZIZMOR_VERSION=1.24.1
|
||||
ENV ZIZMOR_VERSION=${ZIZMOR_VERSION}
|
||||
|
||||
# Pinned here, not fetched with the artefact: a compromised release ships its own checksum.
|
||||
ARG TRIVY_SHA256_AMD64=2edd39da482bb4e9831962487b68f68e3928ec3137794757f54d00383d79547b
|
||||
ARG TRIVY_SHA256_ARM64=13833d97e8a1a5367471c372a173180157f593bece570e20d5d925fef552f5dd
|
||||
ARG POWERSHELL_SHA256_AMD64=492ff26bb958336bf61e597ce19e07648b4003bd2a08659e02f0e3e0446ebfe0
|
||||
ARG POWERSHELL_SHA256_ARM64=2503b71da3e83635592b092df59a0aca4c3606b4d9b068217bb00be989cb0d56
|
||||
ARG ZIZMOR_SHA256_AMD64=a8000f3c683319a523d3b20df0e75457ba591f049cfcbfa98966631b56733c03
|
||||
ARG ZIZMOR_SHA256_ARM64=d66e37ef8a375fb07939c630ebf9709a6e0f20242bdc3faf672a7ed97e0b768d
|
||||
|
||||
# hadolint ignore=DL3008
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
wget libicu76 libunwind8 libssl3 libcurl4 ca-certificates apt-transport-https gnupg \
|
||||
wget libicu72 libunwind8 libssl3 libcurl4 ca-certificates apt-transport-https gnupg \
|
||||
build-essential pkg-config libzstd-dev zlib1g-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -37,9 +29,6 @@ RUN ARCH=$(uname -m) && \
|
||||
else \
|
||||
echo "Unsupported architecture: $ARCH" && exit 1 ; \
|
||||
fi && \
|
||||
if [ "$ARCH" = "x86_64" ]; then EXPECT="$POWERSHELL_SHA256_AMD64" ; else EXPECT="$POWERSHELL_SHA256_ARM64" ; fi && \
|
||||
echo "$EXPECT /tmp/powershell.tar.gz" > /tmp/powershell.sha256 && \
|
||||
sha256sum -c /tmp/powershell.sha256 && rm /tmp/powershell.sha256 && \
|
||||
mkdir -p /opt/microsoft/powershell/7 && \
|
||||
tar zxf /tmp/powershell.tar.gz -C /opt/microsoft/powershell/7 && \
|
||||
chmod +x /opt/microsoft/powershell/7/pwsh && \
|
||||
@@ -56,9 +45,6 @@ RUN ARCH=$(uname -m) && \
|
||||
echo "Unsupported architecture for Trivy: $ARCH" && exit 1 ; \
|
||||
fi && \
|
||||
wget --progress=dot:giga "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_${TRIVY_ARCH}.tar.gz" -O /tmp/trivy.tar.gz && \
|
||||
if [ "$ARCH" = "x86_64" ]; then EXPECT="$TRIVY_SHA256_AMD64" ; else EXPECT="$TRIVY_SHA256_ARM64" ; fi && \
|
||||
echo "$EXPECT /tmp/trivy.tar.gz" > /tmp/trivy.sha256 && \
|
||||
sha256sum -c /tmp/trivy.sha256 && rm /tmp/trivy.sha256 && \
|
||||
tar zxf /tmp/trivy.tar.gz -C /tmp && \
|
||||
mv /tmp/trivy /usr/local/bin/trivy && \
|
||||
chmod +x /usr/local/bin/trivy && \
|
||||
@@ -77,9 +63,6 @@ RUN ARCH=$(uname -m) && \
|
||||
echo "Unsupported architecture for zizmor: $ARCH" && exit 1 ; \
|
||||
fi && \
|
||||
wget --progress=dot:giga "https://github.com/zizmorcore/zizmor/releases/download/v${ZIZMOR_VERSION}/zizmor-${ZIZMOR_ARCH}.tar.gz" -O /tmp/zizmor.tar.gz && \
|
||||
if [ "$ARCH" = "x86_64" ]; then EXPECT="$ZIZMOR_SHA256_AMD64" ; else EXPECT="$ZIZMOR_SHA256_ARM64" ; fi && \
|
||||
echo "$EXPECT /tmp/zizmor.tar.gz" > /tmp/zizmor.sha256 && \
|
||||
sha256sum -c /tmp/zizmor.sha256 && rm /tmp/zizmor.sha256 && \
|
||||
mkdir -p /tmp/zizmor-extract && \
|
||||
tar zxf /tmp/zizmor.tar.gz -C /tmp/zizmor-extract && \
|
||||
mv /tmp/zizmor-extract/zizmor /usr/local/bin/zizmor && \
|
||||
@@ -106,7 +89,7 @@ ENV HOME='/home/prowler'
|
||||
ENV PATH="${HOME}/.local/bin:${PATH}"
|
||||
#hadolint ignore=DL3013
|
||||
RUN pip install --no-cache-dir --upgrade pip && \
|
||||
pip install --no-cache-dir uv==0.12.0
|
||||
pip install --no-cache-dir uv==0.11.14
|
||||
|
||||
RUN uv sync --locked --compile-bytecode && \
|
||||
rm -rf ~/.cache/uv
|
||||
@@ -122,9 +105,6 @@ RUN apt-get purge -y --auto-remove \
|
||||
pkg-config \
|
||||
libzstd-dev \
|
||||
zlib1g-dev \
|
||||
wget \
|
||||
gnupg \
|
||||
apt-transport-https \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
USER prowler
|
||||
@@ -133,15 +113,5 @@ USER prowler
|
||||
RUN pip uninstall dash-html-components -y && \
|
||||
pip uninstall dash-core-components -y
|
||||
|
||||
USER root
|
||||
|
||||
# pip is build-only; the entrypoint runs the venv directly.
|
||||
RUN rm -rf /usr/local/lib/python3.12/site-packages/pip \
|
||||
/usr/local/lib/python3.12/site-packages/pip-*.dist-info \
|
||||
/home/prowler/.local/lib/python3.12/site-packages/pip \
|
||||
/home/prowler/.local/lib/python3.12/site-packages/pip-*.dist-info \
|
||||
/usr/local/bin/pip /usr/local/bin/pip3 /usr/local/bin/pip3.12 \
|
||||
/home/prowler/.local/bin/pip /home/prowler/.local/bin/pip3 /home/prowler/.local/bin/pip3.12
|
||||
|
||||
USER prowler
|
||||
ENTRYPOINT ["/home/prowler/.venv/bin/prowler"]
|
||||
|
||||
@@ -34,9 +34,6 @@ test: ## Test with pytest
|
||||
rm -rf .coverage && \
|
||||
pytest -n auto -vvv -s --cov=./prowler --cov-report=xml tests
|
||||
|
||||
test-mcp: ## Test MCP server with pytest (mirrors CI)
|
||||
cd mcp_server && uv run pytest --cov=./prowler_mcp_server --cov-report=term-missing tests
|
||||
|
||||
coverage: ## Show Test Coverage
|
||||
coverage run --skip-covered -m pytest -v && \
|
||||
coverage report -m && \
|
||||
|
||||
@@ -126,12 +126,12 @@ Every AWS provider scan will enqueue an Attack Paths ingestion job automatically
|
||||
|
||||
| Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.com/user-guide/compliance/tutorials/compliance) | [Categories](https://docs.prowler.com/user-guide/cli/tutorials/misc#categories) | Support | Interface |
|
||||
|---|---|---|---|---|---|---|
|
||||
| AWS | 639 | 86 | 47 | 19 | Official | UI, API, CLI |
|
||||
| AWS | 621 | 86 | 47 | 19 | Official | UI, API, CLI |
|
||||
| Azure | 191 | 22 | 21 | 16 | Official | UI, API, CLI |
|
||||
| GCP | 109 | 20 | 19 | 12 | Official | UI, API, CLI |
|
||||
| Kubernetes | 92 | 7 | 8 | 11 | Official | UI, API, CLI |
|
||||
| GitHub | 24 | 3 | 2 | 5 | Official | UI, API, CLI |
|
||||
| M365 | 143 | 10 | 6 | 10 | Official | UI, API, CLI |
|
||||
| M365 | 111 | 10 | 6 | 10 | Official | UI, API, CLI |
|
||||
| OCI | 52 | 14 | 5 | 10 | Official | UI, API, CLI |
|
||||
| Alibaba Cloud | 63 | 9 | 6 | 9 | Official | UI, API, CLI |
|
||||
| Cloudflare | 29 | 3 | 2 | 5 | Official | UI, API, CLI |
|
||||
@@ -147,7 +147,7 @@ Every AWS provider scan will enqueue an Attack Paths ingestion job automatically
|
||||
| Huawei Cloud [Contact us](https://prowler.com/contact) | 25 | 10 | 1 | 6 | Unofficial | CLI |
|
||||
| E2E Networks [Contact us](https://prowler.com/contact) | 27 | 6 | 0 | 2 | Unofficial | CLI |
|
||||
| Scaleway [Contact us](https://prowler.com/contact) | 1 | 1 | 1 | 1 | Unofficial | CLI |
|
||||
| StackIT [Contact us](https://prowler.com/contact) | 8 | 2 | 1 | 3 | Unofficial | CLI |
|
||||
| StackIT [Contact us](https://prowler.com/contact) | 7 | 2 | 1 | 3 | Unofficial | CLI |
|
||||
| NHN | 6 | 2 | 2 | 0 | Unofficial | CLI |
|
||||
|
||||
> [!Note]
|
||||
|
||||
@@ -4,86 +4,6 @@ All notable changes to the **Prowler API** are documented in this file.
|
||||
|
||||
<!-- changelog: release notes start -->
|
||||
|
||||
## [1.40.0] (Prowler v5.39.0)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
- `GET /api/v1/users/me` membership relationships identify the active tenant with `meta.active` for JWT and API key authentication [(#12388)](https://github.com/prowler-cloud/prowler/pull/12388)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Tenant deletion no longer leaves memberships partially removed when exclusive-user cleanup fails [(#12379)](https://github.com/prowler-cloud/prowler/pull/12379)
|
||||
- `/api/v1/accounts/saml/{organization_slug}/acs/` rejects non-POST requests before SAML response processing [(#12393)](https://github.com/prowler-cloud/prowler/pull/12393)
|
||||
- Social login derives a valid user name when identity providers omit the profile name [(#12413)](https://github.com/prowler-cloud/prowler/pull/12413)
|
||||
|
||||
---
|
||||
|
||||
## [1.39.0] (Prowler v5.38.0)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- Attack Paths adds 20 AWS privilege-escalation detection queries from pathfinding.cloud, covering service PassRole escalations (Batch, Braket, Cognito Identity, ECS, EMR, EMR Serverless, GameLift, Glue, EC2 Image Builder, Kinesis Analytics, HealthOmics, EventBridge Scheduler, SSM, Step Functions), CodeDeploy and Step Functions existing-resource abuse, role permissions-boundary removal with role assumption, and IAM Identity Center permission-set policy injection [(#12237)](https://github.com/prowler-cloud/prowler/pull/12237)
|
||||
- Attack Paths query metadata now carries an outcome (Code execution, Privilege escalation, Public exposure, or Resource inventory), exposed on the queries endpoint so the graph can show a terminal outcome node [(#12344)](https://github.com/prowler-cloud/prowler/pull/12344)
|
||||
- Container images now ship an SBOM and build provenance as OCI attestations [(#12352)](https://github.com/prowler-cloud/prowler/pull/12352)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
- Pin the container vulnerability scanner to Trivy v0.72.0, matching prowler-registry and partner-portal [(#12346)](https://github.com/prowler-cloud/prowler/pull/12346)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Compliance report output directory failures are now logged with the exception attached and fingerprinted by `errno` in Sentry, so `ENOSPC`, `ENOENT` and `EACCES` no longer share a single issue [(#12142)](https://github.com/prowler-cloud/prowler/pull/12142)
|
||||
- Restored the SDK dependency to `@master` now that the dependency bumps have landed there, and regenerated the lock. The API image no longer builds against a temporary integration branch [(#12309)](https://github.com/prowler-cloud/prowler/pull/12309)
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- The API container image now verifies the checksum of every third-party binary it downloads (PowerShell, Trivy, zizmor) before installing it [(#12334)](https://github.com/prowler-cloud/prowler/pull/12334)
|
||||
- Upgrade aiohttp to 3.14.3 to pick up the fix for CVE-2026-69244 [(#12340)](https://github.com/prowler-cloud/prowler/pull/12340)
|
||||
- Upgrade cryptography to 50.0.0, closing CVE-2026-69247 and CVE-2026-69249 [(#12356)](https://github.com/prowler-cloud/prowler/pull/12356)
|
||||
|
||||
---
|
||||
|
||||
## [1.38.1] (Prowler v5.37.1)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Entra Conditional Access guest-user checks no longer report false FAILs in M365 scans: microsoft-kiota packages overridden to 1.9.10 so `guestOrExternalUserTypes` (a flags enum Graph serializes as a comma-separated string) deserializes correctly instead of returning an empty list [(#12315)](https://github.com/prowler-cloud/prowler/pull/12315)
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- The API container image now builds on Debian 13 (trixie), taking its critical CVE count from 18 to 4 [(#12311)](https://github.com/prowler-cloud/prowler/pull/12311)
|
||||
- Bumped PowerShell, Trivy and uv in the API container image, clearing 14 high-severity CVEs [(#12311)](https://github.com/prowler-cloud/prowler/pull/12311)
|
||||
- Bumped `workos` and `pyopenssl` so the API can move to `cryptography` 48.0.1 [(#12311)](https://github.com/prowler-cloud/prowler/pull/12311)
|
||||
- Removed `gnupg` and `apt-transport-https` from the API container image [(#12311)](https://github.com/prowler-cloud/prowler/pull/12311)
|
||||
- The API container image no longer ships `git`; removing it also dropped `perl`, `perl-modules`, `libperl` and `liberror-perl`, clearing 12 critical CVEs. Only `perl-base` remains, which Debian marks Essential and cannot be removed [(#12311)](https://github.com/prowler-cloud/prowler/pull/12311)
|
||||
- Removed `pip` from the API container image, clearing two high-severity CVEs in the vendored copies of `setuptools` and `msgpack` [(#12311)](https://github.com/prowler-cloud/prowler/pull/12311)
|
||||
- Bumped `pillow` to 12.3.0, `httplib2` to 0.32.0 and `pyasn1` to 0.6.4 to resolve known CVEs [(#12311)](https://github.com/prowler-cloud/prowler/pull/12311)
|
||||
|
||||
---
|
||||
|
||||
## [1.38.0] (Prowler v5.37.0)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- Attack Paths: four AWS privilege-escalation detection queries from pathfinding.cloud: cross-account role trust (STS-002), wildcard role trust (STS-003), user permissions-boundary removal (IAM-022), and IAM Identity Center permission-set escalation (SSO-001) [(#11460)](https://github.com/prowler-cloud/prowler/pull/11460)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Attack Paths IAM privilege-escalation queries no longer build an all-nodes × all-resource-items cartesian product, fixing runtime errors and timeouts on accounts with many IAM roles, users, or groups [(#12136)](https://github.com/prowler-cloud/prowler/pull/12136)
|
||||
- `task_args` serialization no longer returns HTTP 500 errors when Celery truncates stored task keyword arguments [(#12165)](https://github.com/prowler-cloud/prowler/pull/12165)
|
||||
- Attack Paths predefined queries on migrated graphs are now scoped with the provider label, letting the graph database seed from its label index instead of a global label scan and preventing query timeouts on Neptune [(#12167)](https://github.com/prowler-cloud/prowler/pull/12167)
|
||||
- Authentication with an API key whose owning user was deleted now returns `401` instead of an unhandled `AttributeError`, and user deletion now revokes the user's API keys across all their tenants [(#12210)](https://github.com/prowler-cloud/prowler/pull/12210)
|
||||
- AWS Security Hub integrations now persist successful connection checks during finding delivery so their connection status and last checked timestamp stay current [(#12212)](https://github.com/prowler-cloud/prowler/pull/12212)
|
||||
- SAML users without a `userType` attribute and without an existing role in the SAML tenant now receive a least-privilege `read_only` fallback role; a numeric suffix is used when that name belongs to a role with different permissions [(#12223)](https://github.com/prowler-cloud/prowler/pull/12223)
|
||||
- Social signups create users and authentication records in one database transaction, preventing incomplete accounts when provisioning fails [(#12245)](https://github.com/prowler-cloud/prowler/pull/12245)
|
||||
- Requesting integrations with a sparse fieldset that leaves out `configuration` no longer returns HTTP 500 errors when the tenant has a Jira integration [(#12261)](https://github.com/prowler-cloud/prowler/pull/12261)
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- Provider deletion and connection checks, scan creation, provider secrets, provider groups, and daily schedules now respect role provider-group visibility [(#12216)](https://github.com/prowler-cloud/prowler/pull/12216)
|
||||
|
||||
---
|
||||
|
||||
## [1.37.0] (Prowler v5.36.0)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
+6
-37
@@ -1,31 +1,23 @@
|
||||
FROM python:3.12.13-slim-trixie@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de AS build
|
||||
FROM python:3.12.13-slim-bookworm@sha256:8a7e7cc04fd3e2bd787f7f24e22d5d119aa590d429b50c95dfe12b3abe52f48b AS build
|
||||
|
||||
LABEL maintainer="https://github.com/prowler-cloud/api"
|
||||
|
||||
ARG POWERSHELL_VERSION=7.5.9
|
||||
ARG POWERSHELL_VERSION=7.5.0
|
||||
ENV POWERSHELL_VERSION=${POWERSHELL_VERSION}
|
||||
# Opt out of PowerShell telemetry (Application Insights -> dc.services.visualstudio.com)
|
||||
ENV POWERSHELL_TELEMETRY_OPTOUT=1
|
||||
|
||||
ARG TRIVY_VERSION=0.73.0
|
||||
ARG TRIVY_VERSION=0.71.2
|
||||
ENV TRIVY_VERSION=${TRIVY_VERSION}
|
||||
|
||||
ARG ZIZMOR_VERSION=1.24.1
|
||||
ENV ZIZMOR_VERSION=${ZIZMOR_VERSION}
|
||||
|
||||
# Pinned here, not fetched with the artefact: a compromised release ships its own checksum.
|
||||
ARG TRIVY_SHA256_AMD64=2edd39da482bb4e9831962487b68f68e3928ec3137794757f54d00383d79547b
|
||||
ARG TRIVY_SHA256_ARM64=13833d97e8a1a5367471c372a173180157f593bece570e20d5d925fef552f5dd
|
||||
ARG POWERSHELL_SHA256_AMD64=492ff26bb958336bf61e597ce19e07648b4003bd2a08659e02f0e3e0446ebfe0
|
||||
ARG POWERSHELL_SHA256_ARM64=2503b71da3e83635592b092df59a0aca4c3606b4d9b068217bb00be989cb0d56
|
||||
ARG ZIZMOR_SHA256_AMD64=a8000f3c683319a523d3b20df0e75457ba591f049cfcbfa98966631b56733c03
|
||||
ARG ZIZMOR_SHA256_ARM64=d66e37ef8a375fb07939c630ebf9709a6e0f20242bdc3faf672a7ed97e0b768d
|
||||
|
||||
# hadolint ignore=DL3008
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
wget \
|
||||
git \
|
||||
libicu76 \
|
||||
libicu72 \
|
||||
gcc \
|
||||
g++ \
|
||||
make \
|
||||
@@ -36,6 +28,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libtool \
|
||||
libxslt1-dev \
|
||||
python3-dev \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install PowerShell
|
||||
@@ -47,9 +40,6 @@ RUN ARCH=$(uname -m) && \
|
||||
else \
|
||||
echo "Unsupported architecture: $ARCH" && exit 1 ; \
|
||||
fi && \
|
||||
if [ "$ARCH" = "x86_64" ]; then EXPECT="$POWERSHELL_SHA256_AMD64" ; else EXPECT="$POWERSHELL_SHA256_ARM64" ; fi && \
|
||||
echo "$EXPECT /tmp/powershell.tar.gz" > /tmp/powershell.sha256 && \
|
||||
sha256sum -c /tmp/powershell.sha256 && rm /tmp/powershell.sha256 && \
|
||||
mkdir -p /opt/microsoft/powershell/7 && \
|
||||
tar zxf /tmp/powershell.tar.gz -C /opt/microsoft/powershell/7 && \
|
||||
chmod +x /opt/microsoft/powershell/7/pwsh && \
|
||||
@@ -66,9 +56,6 @@ RUN ARCH=$(uname -m) && \
|
||||
echo "Unsupported architecture for Trivy: $ARCH" && exit 1 ; \
|
||||
fi && \
|
||||
wget --progress=dot:giga "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_${TRIVY_ARCH}.tar.gz" -O /tmp/trivy.tar.gz && \
|
||||
if [ "$ARCH" = "x86_64" ]; then EXPECT="$TRIVY_SHA256_AMD64" ; else EXPECT="$TRIVY_SHA256_ARM64" ; fi && \
|
||||
echo "$EXPECT /tmp/trivy.tar.gz" > /tmp/trivy.sha256 && \
|
||||
sha256sum -c /tmp/trivy.sha256 && rm /tmp/trivy.sha256 && \
|
||||
tar zxf /tmp/trivy.tar.gz -C /tmp && \
|
||||
mv /tmp/trivy /usr/local/bin/trivy && \
|
||||
chmod +x /usr/local/bin/trivy && \
|
||||
@@ -87,9 +74,6 @@ RUN ARCH=$(uname -m) && \
|
||||
echo "Unsupported architecture for zizmor: $ARCH" && exit 1 ; \
|
||||
fi && \
|
||||
wget --progress=dot:giga "https://github.com/zizmorcore/zizmor/releases/download/v${ZIZMOR_VERSION}/zizmor-${ZIZMOR_ARCH}.tar.gz" -O /tmp/zizmor.tar.gz && \
|
||||
if [ "$ARCH" = "x86_64" ]; then EXPECT="$ZIZMOR_SHA256_AMD64" ; else EXPECT="$ZIZMOR_SHA256_ARM64" ; fi && \
|
||||
echo "$EXPECT /tmp/zizmor.tar.gz" > /tmp/zizmor.sha256 && \
|
||||
sha256sum -c /tmp/zizmor.sha256 && rm /tmp/zizmor.sha256 && \
|
||||
mkdir -p /tmp/zizmor-extract && \
|
||||
tar zxf /tmp/zizmor.tar.gz -C /tmp/zizmor-extract && \
|
||||
mv /tmp/zizmor-extract/zizmor /usr/local/bin/zizmor && \
|
||||
@@ -110,7 +94,7 @@ RUN mkdir -p /tmp/prowler_api_output
|
||||
COPY --chown=prowler:prowler pyproject.toml uv.lock ./
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade pip && \
|
||||
pip install --no-cache-dir uv==0.12.0
|
||||
pip install --no-cache-dir uv==0.11.14
|
||||
|
||||
ENV PATH="/home/prowler/.local/bin:$PATH"
|
||||
|
||||
@@ -125,34 +109,19 @@ RUN .venv/bin/python -m prowler.providers.m365.lib.powershell.m365_powershell
|
||||
USER root
|
||||
|
||||
# Remove build-only packages from the final image after Python dependencies are installed.
|
||||
# git is only needed by uv sync for the `prowler @ git+...` dependency; purging it drops perl too.
|
||||
# wget stays: the compose healthcheck shells out to it.
|
||||
RUN apt-get purge -y --auto-remove \
|
||||
gcc \
|
||||
g++ \
|
||||
git \
|
||||
make \
|
||||
libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libxmlsec1-openssl \
|
||||
libxmlsec1t64 \
|
||||
libxmlsec1t64-openssl \
|
||||
pkg-config \
|
||||
libtool \
|
||||
libxslt1-dev \
|
||||
python3-dev \
|
||||
gnupg \
|
||||
apt-transport-https \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# pip is build-only; the entrypoint runs uv against the prepared venv. uv stays.
|
||||
RUN rm -rf /usr/local/lib/python3.12/site-packages/pip \
|
||||
/usr/local/lib/python3.12/site-packages/pip-*.dist-info \
|
||||
/home/prowler/.local/lib/python3.12/site-packages/pip \
|
||||
/home/prowler/.local/lib/python3.12/site-packages/pip-*.dist-info \
|
||||
/usr/local/bin/pip /usr/local/bin/pip3 /usr/local/bin/pip3.12 \
|
||||
/home/prowler/.local/bin/pip /home/prowler/.local/bin/pip3 /home/prowler/.local/bin/pip3.12
|
||||
|
||||
USER prowler
|
||||
|
||||
COPY --chown=prowler:prowler src/backend/ ./backend/
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Authentication with an API key whose owning user was deleted now returns `401` instead of an unhandled `AttributeError`, and user deletion now revokes the user's API keys across all their tenants
|
||||
@@ -1 +0,0 @@
|
||||
Trivy from v0.72.0 to v0.73.0 in the container image, fixing HIGH CVE-2026-46600 in the bundled `golang.org/x/net`
|
||||
@@ -0,0 +1 @@
|
||||
Attack Paths IAM privilege-escalation queries no longer build an all-nodes × all-resource-items cartesian product, fixing runtime errors and timeouts on accounts with many IAM roles, users, or groups
|
||||
@@ -0,0 +1 @@
|
||||
Attack Paths adds four AWS privilege-escalation detection queries from pathfinding.cloud: cross-account role trust (STS-002), wildcard role trust (STS-003), user permissions-boundary removal (IAM-022), and IAM Identity Center permission-set escalation (SSO-001)
|
||||
@@ -0,0 +1 @@
|
||||
Attack Paths predefined queries on migrated graphs are now scoped with the provider label, letting the graph database seed from its label index instead of a global label scan and preventing query timeouts on Neptune
|
||||
@@ -0,0 +1 @@
|
||||
`task_args` serialization no longer returns HTTP 500 errors when Celery truncates stored task keyword arguments
|
||||
@@ -0,0 +1 @@
|
||||
Provider deletion and connection checks, scan creation, provider secrets, provider groups, and daily schedules now respect role provider-group visibility
|
||||
@@ -0,0 +1 @@
|
||||
SAML users without a `userType` attribute and without an existing role in the SAML tenant now receive a least-privilege `read_only` fallback role; a numeric suffix is used when that name belongs to a role with different permissions
|
||||
@@ -0,0 +1 @@
|
||||
AWS Security Hub integrations now persist successful connection checks during finding delivery so their connection status and last checked timestamp stay current
|
||||
@@ -0,0 +1 @@
|
||||
Social signups create users and authentication records in one database transaction, preventing incomplete accounts when provisioning fails
|
||||
+25
-38
@@ -63,7 +63,7 @@ dependencies = [
|
||||
"werkzeug (==3.1.7)",
|
||||
"sqlparse (==0.5.5)",
|
||||
"fonttools (==4.62.1)",
|
||||
"uvicorn-worker (==0.4.0)"
|
||||
"uvicorn-worker (==0.4.0)",
|
||||
]
|
||||
description = "Prowler's API (Django/DRF)"
|
||||
license = "Apache-2.0"
|
||||
@@ -71,7 +71,7 @@ name = "prowler-api"
|
||||
package-mode = false
|
||||
# Needed for the SDK compatibility
|
||||
requires-python = ">=3.11,<3.13"
|
||||
version = "1.41.0"
|
||||
version = "1.38.0"
|
||||
|
||||
# Shared ruff baseline (kept in sync with mcp_server/pyproject.toml).
|
||||
# target-version tracks this project's lowest supported Python.
|
||||
@@ -92,8 +92,6 @@ extend-select = [
|
||||
|
||||
[tool.uv]
|
||||
# Transitive pins matching master to avoid silent drift; bump deliberately.
|
||||
# workos and pyopenssl run ahead of master: the versions master pins cap cryptography
|
||||
# below 48, so both were bumped to versions that allow it (PROWLER-2310).
|
||||
constraint-dependencies = [
|
||||
"about-time==4.2.1",
|
||||
"adal==1.2.7",
|
||||
@@ -101,7 +99,7 @@ constraint-dependencies = [
|
||||
"aiobotocore==2.25.1",
|
||||
"aiofiles==24.1.0",
|
||||
"aiohappyeyeballs==2.6.1",
|
||||
"aiohttp==3.14.3",
|
||||
"aiohttp==3.14.0",
|
||||
"aioitertools==0.13.0",
|
||||
"aiosignal==1.4.0",
|
||||
"alibabacloud-actiontrail20200706==2.4.1",
|
||||
@@ -130,7 +128,7 @@ constraint-dependencies = [
|
||||
"alibabacloud-sls20201230==5.9.0",
|
||||
"alibabacloud-sts20150401==1.1.6",
|
||||
"alibabacloud-tea==0.4.3",
|
||||
"alibabacloud-tea-openapi==0.4.5",
|
||||
"alibabacloud-tea-openapi==0.4.4",
|
||||
"alibabacloud-tea-util==0.3.14",
|
||||
"alibabacloud-tea-xml==0.0.3",
|
||||
"alibabacloud-vpc20160428==6.13.0",
|
||||
@@ -212,9 +210,9 @@ constraint-dependencies = [
|
||||
"coverage==7.5.4",
|
||||
"cron-descriptor==1.4.5",
|
||||
"crowdstrike-falconpy==1.6.0",
|
||||
"cryptography==50.0.0",
|
||||
"cryptography==46.0.7",
|
||||
"cycler==0.12.1",
|
||||
"darabonba-core==1.0.8",
|
||||
"darabonba-core==1.0.5",
|
||||
"dash==3.1.1",
|
||||
"dash-bootstrap-components==2.0.3",
|
||||
"debugpy==1.8.20",
|
||||
@@ -279,7 +277,7 @@ constraint-dependencies = [
|
||||
"h2==4.3.0",
|
||||
"hpack==4.1.0",
|
||||
"httpcore==1.0.9",
|
||||
"httplib2==0.32.0",
|
||||
"httplib2==0.31.2",
|
||||
"httpx==0.28.1",
|
||||
"humanfriendly==10.0",
|
||||
"hyperframe==6.1.0",
|
||||
@@ -316,13 +314,13 @@ constraint-dependencies = [
|
||||
"matplotlib==3.10.8",
|
||||
"mccabe==0.7.0",
|
||||
"mdurl==0.1.2",
|
||||
"microsoft-kiota-abstractions==1.9.10",
|
||||
"microsoft-kiota-authentication-azure==1.9.10",
|
||||
"microsoft-kiota-http==1.9.10",
|
||||
"microsoft-kiota-serialization-form==1.9.10",
|
||||
"microsoft-kiota-serialization-json==1.9.10",
|
||||
"microsoft-kiota-serialization-multipart==1.9.10",
|
||||
"microsoft-kiota-serialization-text==1.9.10",
|
||||
"microsoft-kiota-abstractions==1.9.9",
|
||||
"microsoft-kiota-authentication-azure==1.9.9",
|
||||
"microsoft-kiota-http==1.9.9",
|
||||
"microsoft-kiota-serialization-form==1.9.9",
|
||||
"microsoft-kiota-serialization-json==1.9.9",
|
||||
"microsoft-kiota-serialization-multipart==1.9.9",
|
||||
"microsoft-kiota-serialization-text==1.9.9",
|
||||
"microsoft-security-utilities-secret-masker==1.0.0b4",
|
||||
"msal==1.35.0b1",
|
||||
"msal-extensions==1.2.0",
|
||||
@@ -339,7 +337,7 @@ constraint-dependencies = [
|
||||
"nltk==3.9.4",
|
||||
"numpy==2.2.6",
|
||||
"oauthlib==3.3.1",
|
||||
"oci==2.183.0",
|
||||
"oci==2.169.0",
|
||||
"openai==1.109.1",
|
||||
"openstacksdk==4.2.0",
|
||||
"opentelemetry-api==1.39.1",
|
||||
@@ -351,7 +349,7 @@ constraint-dependencies = [
|
||||
"pagerduty==6.1.0",
|
||||
"pandas==2.2.3",
|
||||
"pbr==7.0.3",
|
||||
"pillow==12.3.0",
|
||||
"pillow==12.2.0",
|
||||
"pkginfo==1.12.1.2",
|
||||
"platformdirs==4.5.1",
|
||||
"plotly==6.5.2",
|
||||
@@ -367,8 +365,8 @@ constraint-dependencies = [
|
||||
"psycopg2-binary==2.9.9",
|
||||
"py-deviceid==0.1.1",
|
||||
"py-iam-expand==0.3.0",
|
||||
"py-ocsf-models==0.10.0",
|
||||
"pyasn1==0.6.4",
|
||||
"py-ocsf-models==0.8.1",
|
||||
"pyasn1==0.6.3",
|
||||
"pyasn1-modules==0.4.2",
|
||||
"pycodestyle==2.14.0",
|
||||
"pycparser==3.0",
|
||||
@@ -380,7 +378,7 @@ constraint-dependencies = [
|
||||
"pylint==3.2.5",
|
||||
"pymsalruntime==0.18.1",
|
||||
"pynacl==1.6.2",
|
||||
"pyopenssl==26.2.0",
|
||||
"pyopenssl==26.0.0",
|
||||
"pyparsing==3.3.2",
|
||||
"pyreadline3==3.5.4",
|
||||
"pysocks==1.7.1",
|
||||
@@ -449,7 +447,7 @@ constraint-dependencies = [
|
||||
"wcwidth==0.5.3",
|
||||
"websocket-client==1.9.0",
|
||||
"werkzeug==3.1.7",
|
||||
"workos==8.3.0",
|
||||
"workos==6.0.8",
|
||||
"wrapt==1.17.3",
|
||||
"xlsxwriter==3.2.9",
|
||||
"xmlsec==1.3.17",
|
||||
@@ -468,13 +466,10 @@ constraint-dependencies = [
|
||||
# 0.138.1 requires azure-mgmt-containerservice>=41.0.0. Attack Paths does not
|
||||
# ingest Azure today, so override the Cartography dependency to the Prowler pin.
|
||||
#
|
||||
# prowler@master hard-pins the microsoft-kiota packages in [project.dependencies].
|
||||
# microsoft-kiota-serialization-json 1.9.10 fixes get_collection_of_enum_values
|
||||
# returning [] for flags enums serialized as CSV strings (microsoft/kiota-python#515),
|
||||
# which broke the Entra Conditional Access guest-user checks; the kiota packages
|
||||
# release in lockstep and 1.9.10 requires microsoft-kiota-abstractions>=1.9.10, which
|
||||
# a constraint cannot satisfy against the SDK's hard pins, so override the whole set
|
||||
# to 1.9.10 until the SDK bump propagates to the pinned master rev.
|
||||
# prowler@master hard-pins microsoft-kiota-abstractions==1.9.2 in [project.dependencies].
|
||||
# The microsoft-kiota-http security bump to 1.9.9 (GHSA-7j59-v9qr-6fq9) requires
|
||||
# microsoft-kiota-abstractions>=1.9.9, which a constraint cannot satisfy against the
|
||||
# SDK's hard pin; override it to the patched, kiota-aligned version.
|
||||
#
|
||||
# prowler@master hard-pins dulwich==0.23.0 and pyjwt==2.12.1 in [project.dependencies].
|
||||
# dulwich 1.2.5 patches GHSA-897w-fcg9-f6xj (arbitrary file write) and pyjwt 2.13.0
|
||||
@@ -485,16 +480,8 @@ constraint-dependencies = [
|
||||
# that request pyjwt[crypto] and leave cryptography (needed for RS256) only transitive.
|
||||
override-dependencies = [
|
||||
"okta==3.4.2",
|
||||
# alibabacloud-tea-openapi 0.4.5 caps cryptography below 49 and is the latest release.
|
||||
"cryptography==50.0.0",
|
||||
"azure-mgmt-containerservice==34.1.0",
|
||||
"microsoft-kiota-abstractions==1.9.10",
|
||||
"microsoft-kiota-authentication-azure==1.9.10",
|
||||
"microsoft-kiota-http==1.9.10",
|
||||
"microsoft-kiota-serialization-form==1.9.10",
|
||||
"microsoft-kiota-serialization-json==1.9.10",
|
||||
"microsoft-kiota-serialization-multipart==1.9.10",
|
||||
"microsoft-kiota-serialization-text==1.9.10",
|
||||
"microsoft-kiota-abstractions==1.9.9",
|
||||
"dulwich==1.2.5",
|
||||
"pyjwt[crypto]==2.13.0"
|
||||
]
|
||||
|
||||
@@ -12,37 +12,11 @@ from api.models import (
|
||||
UserRoleRelationship,
|
||||
)
|
||||
from api.utils import accept_invitation_for_user
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponseForbidden
|
||||
|
||||
|
||||
class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter):
|
||||
@staticmethod
|
||||
def _get_social_account_name(extra_data: dict, email: str) -> str:
|
||||
name_field = User._meta.get_field("name")
|
||||
for value in (
|
||||
extra_data.get("name"),
|
||||
extra_data.get("login"),
|
||||
extra_data.get("username"),
|
||||
email,
|
||||
):
|
||||
if not isinstance(value, str):
|
||||
continue
|
||||
|
||||
candidate = value.strip()[: name_field.max_length].rstrip()
|
||||
if not candidate:
|
||||
continue
|
||||
|
||||
try:
|
||||
name_field.run_validators(candidate)
|
||||
except ValidationError:
|
||||
continue
|
||||
|
||||
return candidate
|
||||
|
||||
raise ValueError("Social account does not provide a valid user identity.")
|
||||
|
||||
@staticmethod
|
||||
def get_user_by_email(email: str):
|
||||
try:
|
||||
@@ -142,8 +116,11 @@ class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter):
|
||||
|
||||
if provider != "saml":
|
||||
# Handle other providers (e.g., GitHub, Google)
|
||||
user.name = self._get_social_account_name(extra, user.email)
|
||||
user.save(using=MainRouter.admin_db)
|
||||
social_account_name = extra.get("name")
|
||||
if social_account_name:
|
||||
user.name = social_account_name
|
||||
user.save(using=MainRouter.admin_db)
|
||||
|
||||
invitation_token = self._get_invitation_token(request)
|
||||
if invitation_token:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from api.attack_paths.queries import (
|
||||
AttackPathsQueryDefinition,
|
||||
AttackPathsQueryOutcome,
|
||||
AttackPathsQueryParameterDefinition,
|
||||
get_queries_for_provider,
|
||||
get_query_by_id,
|
||||
@@ -8,7 +7,6 @@ from api.attack_paths.queries import (
|
||||
|
||||
__all__ = [
|
||||
"AttackPathsQueryDefinition",
|
||||
"AttackPathsQueryOutcome",
|
||||
"AttackPathsQueryParameterDefinition",
|
||||
"get_queries_for_provider",
|
||||
"get_query_by_id",
|
||||
|
||||
@@ -4,13 +4,11 @@ from api.attack_paths.queries.registry import (
|
||||
)
|
||||
from api.attack_paths.queries.types import (
|
||||
AttackPathsQueryDefinition,
|
||||
AttackPathsQueryOutcome,
|
||||
AttackPathsQueryParameterDefinition,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AttackPathsQueryDefinition",
|
||||
"AttackPathsQueryOutcome",
|
||||
"AttackPathsQueryParameterDefinition",
|
||||
"get_queries_for_provider",
|
||||
"get_query_by_id",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,38 +1,4 @@
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AttackPathsQueryOutcomeMeta:
|
||||
"""Display metadata for an outcome kind.
|
||||
|
||||
`label` and `partial` are properties of the outcome *kind*, not of an
|
||||
individual query, so they live here once and every query just references a
|
||||
kind. `partial` marks a latent/posture outcome (e.g. inventory) that the UI
|
||||
renders as a marker rather than a full realized outcome.
|
||||
"""
|
||||
|
||||
kind: str
|
||||
label: str
|
||||
partial: bool = False
|
||||
|
||||
|
||||
class AttackPathsQueryOutcome(Enum):
|
||||
"""The terminal impact an attack-path query leads to.
|
||||
|
||||
Set per query and exposed by the API so the UI can render the graph's
|
||||
terminal outcome node. The taxonomy is shared with Prowler Hub's attack-path
|
||||
diagram (whose terminal labels match these values).
|
||||
"""
|
||||
|
||||
CODE_EXECUTION = AttackPathsQueryOutcomeMeta("code_execution", "Code execution")
|
||||
PRIVILEGE_ESCALATION = AttackPathsQueryOutcomeMeta(
|
||||
"privilege_escalation", "Privilege escalation"
|
||||
)
|
||||
PUBLIC_EXPOSURE = AttackPathsQueryOutcomeMeta("public_exposure", "Public exposure")
|
||||
RESOURCE_INVENTORY = AttackPathsQueryOutcomeMeta(
|
||||
"resource_inventory", "Resource inventory", partial=True
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -70,5 +36,4 @@ class AttackPathsQueryDefinition:
|
||||
provider: str
|
||||
cypher: str
|
||||
attribution: AttackPathsQueryAttribution | None = None
|
||||
outcome: AttackPathsQueryOutcome | None = None
|
||||
parameters: list[AttackPathsQueryParameterDefinition] = field(default_factory=list)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Prowler API
|
||||
version: 1.41.0
|
||||
version: 1.38.0
|
||||
description: |-
|
||||
Prowler API specification.
|
||||
|
||||
|
||||
@@ -1590,8 +1590,8 @@ class TestAPIKeyMultiTenantWorkflows:
|
||||
tenant1 = tenants_fixture[0]
|
||||
tenant2 = tenants_fixture[1]
|
||||
|
||||
membership1 = Membership.objects.create(user=user, tenant=tenant1)
|
||||
membership2 = Membership.objects.create(user=user, tenant=tenant2)
|
||||
Membership.objects.create(user=user, tenant=tenant1)
|
||||
Membership.objects.create(user=user, tenant=tenant2)
|
||||
|
||||
role1 = Role.objects.create(
|
||||
tenant_id=tenant1.id,
|
||||
@@ -1646,27 +1646,6 @@ class TestAPIKeyMultiTenantWorkflows:
|
||||
assert me_response1.json()["data"]["id"] == str(user.id)
|
||||
assert me_response2.json()["data"]["id"] == str(user.id)
|
||||
|
||||
memberships1 = {
|
||||
item["id"]: item["meta"]["active"]
|
||||
for item in me_response1.json()["data"]["relationships"]["memberships"][
|
||||
"data"
|
||||
]
|
||||
}
|
||||
memberships2 = {
|
||||
item["id"]: item["meta"]["active"]
|
||||
for item in me_response2.json()["data"]["relationships"]["memberships"][
|
||||
"data"
|
||||
]
|
||||
}
|
||||
assert memberships1 == {
|
||||
str(membership1.id): True,
|
||||
str(membership2.id): False,
|
||||
}
|
||||
assert memberships2 == {
|
||||
str(membership1.id): False,
|
||||
str(membership2.id): True,
|
||||
}
|
||||
|
||||
def test_api_key_cannot_access_different_tenant_resources(
|
||||
self, tenants_fixture, aws_provider
|
||||
):
|
||||
|
||||
@@ -111,110 +111,6 @@ def _verify_local_email(user):
|
||||
)
|
||||
|
||||
|
||||
def test_social_account_name_falls_back_to_login_for_blank_name():
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
|
||||
name = adapter._get_social_account_name(
|
||||
{"name": " ", "login": "octocat"},
|
||||
"verified@example.com",
|
||||
)
|
||||
|
||||
assert name == "octocat"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("provider_name", [None, "", " ", 123, ["name"]])
|
||||
def test_social_account_name_ignores_unusable_provider_names(provider_name):
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
|
||||
name = adapter._get_social_account_name(
|
||||
{"name": provider_name, "login": "octocat"},
|
||||
"verified@example.com",
|
||||
)
|
||||
|
||||
assert name == "octocat"
|
||||
|
||||
|
||||
def test_social_account_name_uses_login_when_name_is_missing():
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
|
||||
name = adapter._get_social_account_name(
|
||||
{"login": "octocat"},
|
||||
"verified@example.com",
|
||||
)
|
||||
|
||||
assert name == "octocat"
|
||||
|
||||
|
||||
def test_social_account_name_falls_back_to_username_then_email():
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
|
||||
username_name = adapter._get_social_account_name(
|
||||
{"name": "ab", "login": None, "username": " monalisa "},
|
||||
"verified@example.com",
|
||||
)
|
||||
email_name = adapter._get_social_account_name({}, " verified@example.com ")
|
||||
|
||||
assert username_name == "monalisa"
|
||||
assert email_name == "verified@example.com"
|
||||
|
||||
|
||||
def test_social_account_name_trims_and_limits_provider_name():
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
max_length = User._meta.get_field("name").max_length
|
||||
|
||||
trimmed_name = adapter._get_social_account_name(
|
||||
{"name": " Ada Lovelace "},
|
||||
"verified@example.com",
|
||||
)
|
||||
limited_name = adapter._get_social_account_name(
|
||||
{"name": "a" * (max_length + 1)},
|
||||
"verified@example.com",
|
||||
)
|
||||
|
||||
assert trimmed_name == "Ada Lovelace"
|
||||
assert limited_name == "a" * max_length
|
||||
|
||||
|
||||
def test_social_account_name_rejects_missing_identity():
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="Social account does not provide a valid user identity",
|
||||
):
|
||||
adapter._get_social_account_name({}, "")
|
||||
|
||||
|
||||
def test_save_user_applies_normalized_social_account_name(rf):
|
||||
adapter = ProwlerSocialAccountAdapter()
|
||||
request = rf.post("/")
|
||||
request.session = {}
|
||||
sociallogin = MagicMock(spec=SocialLogin)
|
||||
sociallogin.provider = MagicMock()
|
||||
sociallogin.provider.id = "github"
|
||||
sociallogin.account = MagicMock()
|
||||
sociallogin.account.extra_data = {"name": None, "login": " octocat "}
|
||||
user = User(email="verified@example.com")
|
||||
user.save = MagicMock()
|
||||
invitation = SimpleNamespace(tenant_id="tenant-id")
|
||||
|
||||
with (
|
||||
patch("api.adapters.super") as mock_super,
|
||||
patch("api.adapters.transaction.atomic"),
|
||||
patch("api.adapters.write_db_alias"),
|
||||
patch.object(adapter, "_get_invitation_token", return_value="token"),
|
||||
patch(
|
||||
"api.adapters.accept_invitation_for_user",
|
||||
return_value=(invitation, True),
|
||||
),
|
||||
):
|
||||
mock_super.return_value.save_user.return_value = user
|
||||
saved_user = adapter.save_user(request, sociallogin)
|
||||
|
||||
assert saved_user.name == "octocat"
|
||||
assert request.prowler_invitation_token == "token"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestProwlerSocialAccountAdapter:
|
||||
def test_get_user_by_email_returns_user(self, create_test_user):
|
||||
|
||||
@@ -22,10 +22,7 @@ from api.attack_paths.queries.aws import (
|
||||
AWS_STS_PRIVESC_CROSS_ACCOUNT_TRUST,
|
||||
AWS_STS_PRIVESC_WILDCARD_TRUST,
|
||||
)
|
||||
from api.attack_paths.queries.types import (
|
||||
AttackPathsQueryDefinition,
|
||||
AttackPathsQueryOutcome,
|
||||
)
|
||||
from api.attack_paths.queries.types import AttackPathsQueryDefinition
|
||||
|
||||
# The pathfinding.cloud privilege-escalation queries added for PROWLER-2278.
|
||||
NEW_PATHFINDING_QUERIES = [
|
||||
@@ -241,50 +238,6 @@ class TestAllQueriesUniqueIds:
|
||||
assert not duplicates, f"Duplicate query IDs found: {duplicates}"
|
||||
|
||||
|
||||
class TestQueryOutcomes:
|
||||
"""Every query carries a valid outcome (the graph's terminal impact)."""
|
||||
|
||||
def test_every_query_has_an_outcome(self):
|
||||
# Completeness guard: a new query must be given an outcome, so the UI can
|
||||
# always render a terminal outcome node.
|
||||
missing = [q.id for q in AWS_QUERIES if q.outcome is None]
|
||||
assert not missing, f"Queries without an outcome: {missing}"
|
||||
|
||||
def test_every_outcome_is_a_valid_member(self):
|
||||
for query in AWS_QUERIES:
|
||||
assert isinstance(query.outcome, AttackPathsQueryOutcome)
|
||||
assert query.outcome.value.kind
|
||||
assert query.outcome.value.label
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"query, expected",
|
||||
[
|
||||
(
|
||||
AWS_STS_PRIVESC_CROSS_ACCOUNT_TRUST,
|
||||
AttackPathsQueryOutcome.PRIVILEGE_ESCALATION,
|
||||
),
|
||||
(
|
||||
AWS_IAM_PRIVESC_DELETE_USER_PERMISSIONS_BOUNDARY,
|
||||
AttackPathsQueryOutcome.PRIVILEGE_ESCALATION,
|
||||
),
|
||||
],
|
||||
ids=lambda v: getattr(v, "id", getattr(v, "name", "")),
|
||||
)
|
||||
def test_representative_outcomes(self, query, expected):
|
||||
assert query.outcome is expected
|
||||
|
||||
def test_inventory_outcome_is_partial(self):
|
||||
assert AttackPathsQueryOutcome.RESOURCE_INVENTORY.value.partial is True
|
||||
|
||||
def test_realized_outcomes_are_not_partial(self):
|
||||
for outcome in (
|
||||
AttackPathsQueryOutcome.CODE_EXECUTION,
|
||||
AttackPathsQueryOutcome.PRIVILEGE_ESCALATION,
|
||||
AttackPathsQueryOutcome.PUBLIC_EXPOSURE,
|
||||
):
|
||||
assert outcome.value.partial is False
|
||||
|
||||
|
||||
def _strip_comment_lines(cypher: str) -> str:
|
||||
"""Drop `//` comment lines so keyword scans ignore prose in comments."""
|
||||
return "\n".join(
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
"""
|
||||
Structural validation for the pathfinding.cloud service privilege-escalation
|
||||
Attack Paths queries added in PROWLER-2279.
|
||||
|
||||
These assert the conventions documented in
|
||||
`docs/developer-guide/attack-paths-queries.mdx`: list-typed policy properties are
|
||||
reached through `HAS_*` child-item traversals (never read as node fields),
|
||||
predicate functions unsupported on Neptune (`any`/`all`/`none`, regex `=~`) are
|
||||
absent, the finding probe is typed and filters only on `status`, and the
|
||||
`RETURN` shape preserves the `paths, dpf, dpfr` contract.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from api.attack_paths.queries.aws import AWS_QUERIES
|
||||
from api.attack_paths.queries.types import AttackPathsQueryDefinition
|
||||
|
||||
# IDs of the queries introduced for PROWLER-2279 (pathfinding.cloud coverage).
|
||||
PATHFINDING_2279_QUERY_IDS = [
|
||||
"aws-batch-privesc-passrole-submit-job",
|
||||
"aws-braket-privesc-passrole-create-job",
|
||||
"aws-cognito-privesc-passrole-set-identity-pool-roles",
|
||||
"aws-ecs-privesc-passrole-start-existing-task",
|
||||
"aws-emr-privesc-passrole-run-job-flow",
|
||||
"aws-emrserverless-privesc-passrole-start-job",
|
||||
"aws-gamelift-privesc-passrole-create-fleet",
|
||||
"aws-glue-privesc-passrole-create-session",
|
||||
"aws-imagebuilder-privesc-passrole-create-image",
|
||||
"aws-kinesisanalytics-privesc-passrole-create-application",
|
||||
"aws-omics-privesc-passrole-start-run",
|
||||
"aws-scheduler-privesc-passrole-create-schedule",
|
||||
"aws-ssm-privesc-passrole-automation",
|
||||
"aws-stepfunctions-privesc-passrole-create-state-machine",
|
||||
"aws-batch-privesc-submit-existing-job",
|
||||
"aws-codedeploy-privesc-create-deployment",
|
||||
"aws-stepfunctions-privesc-update-state-machine",
|
||||
"aws-iam-privesc-delete-role-boundary-assume-role",
|
||||
"aws-sso-privesc-attach-managed-policy-permission-set",
|
||||
"aws-sso-privesc-put-inline-policy-permission-set",
|
||||
]
|
||||
|
||||
_BY_ID = {q.id: q for q in AWS_QUERIES}
|
||||
NEW_QUERIES = [_BY_ID[qid] for qid in PATHFINDING_2279_QUERY_IDS if qid in _BY_ID]
|
||||
|
||||
NEPTUNE_UNSUPPORTED_PREDICATES = re.compile(r"\b(any|all|none)\s*\(", re.IGNORECASE)
|
||||
NORMALIZED_STATEMENT_FIELDS = ("action", "resource", "notaction", "notresource")
|
||||
|
||||
|
||||
def test_all_2279_queries_registered():
|
||||
missing = [qid for qid in PATHFINDING_2279_QUERY_IDS if qid not in _BY_ID]
|
||||
assert not missing, f"queries not registered in AWS_QUERIES: {missing}"
|
||||
|
||||
|
||||
class TestServicePrivescQuerySchema:
|
||||
@pytest.mark.parametrize("query", NEW_QUERIES, ids=lambda q: q.id)
|
||||
def test_is_query_definition(self, query):
|
||||
assert isinstance(query, AttackPathsQueryDefinition)
|
||||
|
||||
@pytest.mark.parametrize("query", NEW_QUERIES, ids=lambda q: q.id)
|
||||
def test_id_kebab_and_aws_prefixed(self, query):
|
||||
assert query.id.startswith("aws-")
|
||||
assert re.match(r"^[a-z0-9]+(-[a-z0-9]+)*$", query.id)
|
||||
|
||||
@pytest.mark.parametrize("query", NEW_QUERIES, ids=lambda q: q.id)
|
||||
def test_provider_is_aws(self, query):
|
||||
assert query.provider == "aws"
|
||||
|
||||
@pytest.mark.parametrize("query", NEW_QUERIES, ids=lambda q: q.id)
|
||||
def test_has_metadata(self, query):
|
||||
assert query.name and len(query.name) > 5
|
||||
assert query.short_description and len(query.short_description) > 10
|
||||
assert query.description and len(query.description) > 20
|
||||
assert isinstance(query.parameters, list)
|
||||
|
||||
@pytest.mark.parametrize("query", NEW_QUERIES, ids=lambda q: q.id)
|
||||
def test_attribution_links_pathfinding(self, query):
|
||||
assert query.attribution is not None
|
||||
assert "pathfinding.cloud" in query.attribution.text
|
||||
assert query.attribution.link.startswith("https://pathfinding.cloud/paths/")
|
||||
|
||||
|
||||
class TestServicePrivescQueryCypher:
|
||||
@pytest.mark.parametrize("query", NEW_QUERIES, ids=lambda q: q.id)
|
||||
def test_anchored_and_provider_scoped(self, query):
|
||||
assert "(aws:AWSAccount {id: $provider_uid})" in query.cypher
|
||||
|
||||
@pytest.mark.parametrize("query", NEW_QUERIES, ids=lambda q: q.id)
|
||||
def test_finding_label_interpolated(self, query):
|
||||
assert "PROWLER_FINDING_LABEL" not in query.cypher
|
||||
|
||||
@pytest.mark.parametrize("query", NEW_QUERIES, ids=lambda q: q.id)
|
||||
def test_typed_status_scoped_finding_probe(self, query):
|
||||
assert re.search(
|
||||
r"-\[pfr:HAS_FINDING\]-\(pf:ProwlerFinding \{status: 'FAIL'\}\)",
|
||||
query.cypher,
|
||||
), f"{query.id} lacks the typed, status-scoped finding probe"
|
||||
|
||||
@pytest.mark.parametrize("query", NEW_QUERIES, ids=lambda q: q.id)
|
||||
def test_return_contract(self, query):
|
||||
assert re.search(
|
||||
r"RETURN paths, collect\(DISTINCT pf\) as dpf, "
|
||||
r"collect\(DISTINCT pfr\) as dpfr",
|
||||
query.cypher,
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("query", NEW_QUERIES, ids=lambda q: q.id)
|
||||
def test_no_neptune_unsupported_predicates(self, query):
|
||||
m = NEPTUNE_UNSUPPORTED_PREDICATES.search(query.cypher)
|
||||
assert m is None, f"{query.id} uses '{m.group().strip()}' (not Neptune-safe)"
|
||||
|
||||
@pytest.mark.parametrize("query", NEW_QUERIES, ids=lambda q: q.id)
|
||||
def test_no_regex_operator(self, query):
|
||||
assert "=~" not in query.cypher
|
||||
|
||||
@pytest.mark.parametrize("query", NEW_QUERIES, ids=lambda q: q.id)
|
||||
def test_does_not_read_normalized_list_fields(self, query):
|
||||
for field in NORMALIZED_STATEMENT_FIELDS:
|
||||
assert not re.search(rf"\.{field}\b", query.cypher), (
|
||||
f"{query.id} reads normalized list field '.{field}' as a property; "
|
||||
f"traverse the HAS_{field.upper()} edge instead"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("query", NEW_QUERIES, ids=lambda q: q.id)
|
||||
def test_read_only(self, query):
|
||||
no_comments = "\n".join(
|
||||
line
|
||||
for line in query.cypher.split("\n")
|
||||
if not line.strip().startswith("//")
|
||||
)
|
||||
assert not re.search(
|
||||
r"\b(CREATE|MERGE|SET|DELETE|REMOVE|DETACH)\b", no_comments, re.IGNORECASE
|
||||
)
|
||||
@@ -2,7 +2,6 @@ import json
|
||||
from unittest.mock import ANY, Mock, patch
|
||||
|
||||
import pytest
|
||||
from api.db_utils import rls_transaction
|
||||
from api.models import (
|
||||
Integration,
|
||||
IntegrationProviderRelationship,
|
||||
@@ -1292,6 +1291,22 @@ class TestLimitedVisibility:
|
||||
response.json()["data"]["relationships"]["providers"]["meta"]["count"] == 1
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def jira_integration(self, tenants_fixture):
|
||||
# Jira is a tenant-wide integration: it is not attached to any provider
|
||||
return Integration.objects.create(
|
||||
tenant_id=tenants_fixture[0].id,
|
||||
enabled=True,
|
||||
connected=True,
|
||||
integration_type=Integration.IntegrationChoices.JIRA,
|
||||
configuration={"projects": {"TEST": "Test project"}},
|
||||
credentials={
|
||||
"domain": "test",
|
||||
"user_mail": "a@b.com",
|
||||
"api_token": "token",
|
||||
},
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def out_of_scope_integration(self, tenants_fixture, provider_factory):
|
||||
tenant_id = tenants_fixture[0].id
|
||||
@@ -1317,7 +1332,7 @@ class TestLimitedVisibility:
|
||||
self,
|
||||
authenticated_client_rbac_limited,
|
||||
integrations_fixture,
|
||||
jira_integration_fixture,
|
||||
jira_integration,
|
||||
aws_provider_pair,
|
||||
):
|
||||
# Integration 2 is attached to both providers, so make both visible to the role
|
||||
@@ -1333,16 +1348,13 @@ class TestLimitedVisibility:
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
integration_ids = [item["id"] for item in response.json()["data"]]
|
||||
# The tenant-wide Jira integration is visible without unlimited visibility
|
||||
assert str(jira_integration_fixture.id) in integration_ids
|
||||
assert str(jira_integration.id) in integration_ids
|
||||
# Integrations attached to more than one visible provider are not duplicated
|
||||
assert integration_ids.count(str(integrations_fixture[1].id)) == 1
|
||||
assert response.json()["meta"]["pagination"]["count"] == len(integration_ids)
|
||||
|
||||
def test_integrations_list_without_provider_groups_keeps_tenant_wide_integration(
|
||||
self,
|
||||
authenticated_client_rbac_limited,
|
||||
integrations_fixture,
|
||||
jira_integration_fixture,
|
||||
self, authenticated_client_rbac_limited, integrations_fixture, jira_integration
|
||||
):
|
||||
# A role with no provider group at all sees no provider, but still needs Jira
|
||||
RoleProviderGroupRelationship.objects.all().delete()
|
||||
@@ -1351,7 +1363,7 @@ class TestLimitedVisibility:
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
integration_ids = [item["id"] for item in response.json()["data"]]
|
||||
assert integration_ids == [str(jira_integration_fixture.id)]
|
||||
assert integration_ids == [str(jira_integration.id)]
|
||||
|
||||
def test_integrations_include_providers_hides_out_of_scope_providers(
|
||||
self, authenticated_client_rbac_limited, integrations_fixture, aws_provider_pair
|
||||
@@ -1370,19 +1382,13 @@ class TestLimitedVisibility:
|
||||
assert str(hidden_provider.id) not in included_ids
|
||||
|
||||
def test_integrations_list_with_sparse_fields(
|
||||
self,
|
||||
authenticated_client_rbac_limited,
|
||||
integrations_fixture,
|
||||
jira_integration_fixture,
|
||||
self, authenticated_client_rbac_limited, integrations_fixture
|
||||
):
|
||||
response = authenticated_client_rbac_limited.get(
|
||||
reverse("integration-list"), {"fields[integrations]": "enabled"}
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert str(jira_integration_fixture.id) in [
|
||||
item["id"] for item in response.json()["data"]
|
||||
]
|
||||
assert all(
|
||||
list(item["attributes"].keys()) == ["enabled"]
|
||||
for item in response.json()["data"]
|
||||
@@ -1418,10 +1424,7 @@ class TestLimitedVisibility:
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
def test_integration_update_allowed_when_fully_visible(
|
||||
self,
|
||||
authenticated_client_rbac_limited,
|
||||
integrations_fixture,
|
||||
jira_integration_fixture,
|
||||
self, authenticated_client_rbac_limited, integrations_fixture, jira_integration
|
||||
):
|
||||
# Integration 1 is only related to provider1, which the role can access
|
||||
integration = integrations_fixture[0]
|
||||
@@ -1455,21 +1458,20 @@ class TestLimitedVisibility:
|
||||
payload = {
|
||||
"data": {
|
||||
"type": "integrations",
|
||||
"id": str(jira_integration_fixture.id),
|
||||
"id": str(jira_integration.id),
|
||||
"attributes": {"enabled": False},
|
||||
}
|
||||
}
|
||||
|
||||
response = authenticated_client_rbac_limited.patch(
|
||||
reverse("integration-detail", kwargs={"pk": jira_integration_fixture.id}),
|
||||
reverse("integration-detail", kwargs={"pk": jira_integration.id}),
|
||||
data=json.dumps(payload),
|
||||
content_type="application/vnd.api+json",
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
with rls_transaction(str(jira_integration_fixture.tenant_id)):
|
||||
jira_integration_fixture.refresh_from_db()
|
||||
assert jira_integration_fixture.enabled is False
|
||||
jira_integration.refresh_from_db()
|
||||
assert jira_integration.enabled is False
|
||||
|
||||
def test_integration_create_rejects_out_of_scope_provider(
|
||||
self, authenticated_client_rbac_limited, aws_provider_pair
|
||||
@@ -1569,10 +1571,7 @@ class TestLimitedVisibility:
|
||||
assert Integration.objects.filter(id=integration.id).exists()
|
||||
|
||||
def test_integration_delete_allowed_when_fully_visible(
|
||||
self,
|
||||
authenticated_client_rbac_limited,
|
||||
integrations_fixture,
|
||||
jira_integration_fixture,
|
||||
self, authenticated_client_rbac_limited, integrations_fixture, jira_integration
|
||||
):
|
||||
# Integration 1 is only related to provider1, which the role can access
|
||||
integration = integrations_fixture[0]
|
||||
@@ -1586,20 +1585,20 @@ class TestLimitedVisibility:
|
||||
|
||||
# Tenant-wide integrations have no provider restricting the role
|
||||
response = authenticated_client_rbac_limited.delete(
|
||||
reverse("integration-detail", kwargs={"pk": jira_integration_fixture.id})
|
||||
reverse("integration-detail", kwargs={"pk": jira_integration.id})
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
||||
def test_jira_issue_types_allowed_without_unlimited_visibility(
|
||||
self, authenticated_client_rbac_limited, jira_integration_fixture
|
||||
self, authenticated_client_rbac_limited, jira_integration
|
||||
):
|
||||
with patch("api.v1.views.initialize_prowler_integration") as mock_jira:
|
||||
mock_jira.return_value.get_available_issue_types.return_value = ["Task"]
|
||||
response = authenticated_client_rbac_limited.get(
|
||||
reverse(
|
||||
"integration-jira-issue-types",
|
||||
kwargs={"integration_pk": jira_integration_fixture.id},
|
||||
kwargs={"integration_pk": jira_integration.id},
|
||||
),
|
||||
{"project_key": "TEST"},
|
||||
)
|
||||
@@ -1635,12 +1634,12 @@ class TestLimitedVisibility:
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
def test_jira_dispatches_allowed_without_unlimited_visibility(
|
||||
self, authenticated_client_rbac_limited, jira_integration_fixture
|
||||
self, authenticated_client_rbac_limited, jira_integration
|
||||
):
|
||||
response = authenticated_client_rbac_limited.post(
|
||||
reverse(
|
||||
"integration-jira-dispatches",
|
||||
kwargs={"integration_pk": jira_integration_fixture.id},
|
||||
kwargs={"integration_pk": jira_integration.id},
|
||||
),
|
||||
data=json.dumps({}),
|
||||
content_type="application/vnd.api+json",
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import errno
|
||||
import logging
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from config.settings import sentry as sentry_settings
|
||||
from config.settings.sentry import before_send, errno_fingerprint
|
||||
from config.settings.sentry import before_send
|
||||
|
||||
|
||||
def test_initialize_sentry_skips_without_dsn():
|
||||
@@ -189,165 +188,3 @@ def test_before_send_passes_non_defunct_neo4j_log():
|
||||
event = MagicMock()
|
||||
|
||||
assert before_send(event, hint) == event
|
||||
|
||||
|
||||
def _filesystem_hint(exception, msg="Error generating output directory"):
|
||||
"""Build the hint the logging integration sends for a filesystem failure."""
|
||||
exc_info = (type(exception), exception, exception.__traceback__)
|
||||
log_record = _make_log_record(msg)
|
||||
log_record.exc_info = exc_info
|
||||
setattr(
|
||||
log_record,
|
||||
sentry_settings.ERROR_CATEGORY_ATTRIBUTE,
|
||||
sentry_settings.FILESYSTEM_ERROR_CATEGORY,
|
||||
)
|
||||
return {"log_record": log_record, "exc_info": exc_info}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("error_number", "message", "expected_suffix"),
|
||||
[
|
||||
(errno.ENOSPC, "No space left on device", "errno:ENOSPC"),
|
||||
(errno.ENOENT, "No such file or directory", "errno:ENOENT"),
|
||||
(errno.EACCES, "Permission denied", "errno:EACCES"),
|
||||
],
|
||||
)
|
||||
def test_before_send_fingerprints_oserror_by_errno(
|
||||
error_number, message, expected_suffix
|
||||
):
|
||||
"""Filesystem failures raised from the same call site must not be merged."""
|
||||
event = {}
|
||||
|
||||
result = before_send(event, _filesystem_hint(OSError(error_number, message)))
|
||||
|
||||
assert result is event
|
||||
assert event["fingerprint"] == ["{{ default }}", expected_suffix]
|
||||
|
||||
|
||||
def test_before_send_fingerprints_differ_per_errno():
|
||||
"""ENOSPC and ENOENT from the same call site produce different issues."""
|
||||
enospc_event = {}
|
||||
enoent_event = {}
|
||||
|
||||
before_send(
|
||||
enospc_event, _filesystem_hint(OSError(errno.ENOSPC, "No space left on device"))
|
||||
)
|
||||
before_send(
|
||||
enoent_event,
|
||||
_filesystem_hint(OSError(errno.ENOENT, "No such file or directory")),
|
||||
)
|
||||
|
||||
assert enospc_event["fingerprint"] != enoent_event["fingerprint"]
|
||||
|
||||
|
||||
def test_before_send_fingerprints_wrapped_oserror():
|
||||
"""The errno is found even when the OSError is wrapped by another error."""
|
||||
try:
|
||||
try:
|
||||
raise OSError(errno.ENOSPC, "No space left on device")
|
||||
except OSError as os_error:
|
||||
raise RuntimeError("Error generating output directory") from os_error
|
||||
except RuntimeError as wrapper:
|
||||
event = {}
|
||||
before_send(event, _filesystem_hint(wrapper))
|
||||
|
||||
assert event["fingerprint"] == ["{{ default }}", "errno:ENOSPC"]
|
||||
|
||||
|
||||
def test_before_send_does_not_fingerprint_non_oserror():
|
||||
"""Non-filesystem exceptions keep Sentry's default grouping."""
|
||||
event = {}
|
||||
|
||||
result = before_send(event, _filesystem_hint(ValueError("boom")))
|
||||
|
||||
assert result is event
|
||||
assert "fingerprint" not in event
|
||||
|
||||
|
||||
def test_before_send_does_not_fingerprint_unrelated_oserror_log():
|
||||
"""Only records declaring the filesystem category opt into the errno grouping."""
|
||||
exception = OSError(errno.ENOSPC, "No space left on device")
|
||||
log_record = _make_log_record("Unrelated failure")
|
||||
exc_info = (OSError, exception, None)
|
||||
log_record.exc_info = exc_info
|
||||
event = {}
|
||||
|
||||
result = before_send(event, {"log_record": log_record, "exc_info": exc_info})
|
||||
|
||||
assert result is event
|
||||
assert "fingerprint" not in event
|
||||
|
||||
|
||||
def test_before_send_does_not_fingerprint_exception_events():
|
||||
"""Exception events without a log record keep Sentry's default grouping."""
|
||||
event = {}
|
||||
|
||||
result = before_send(
|
||||
event,
|
||||
{"exc_info": (OSError, OSError(errno.ENOSPC, "No space left on device"), None)},
|
||||
)
|
||||
|
||||
assert result is event
|
||||
assert "fingerprint" not in event
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fingerprint", [["scope-fingerprint"], []])
|
||||
def test_before_send_keeps_existing_fingerprint(fingerprint):
|
||||
"""A fingerprint set by a scope or an integration is never overwritten."""
|
||||
expected_fingerprint = fingerprint.copy()
|
||||
event = {"fingerprint": fingerprint}
|
||||
|
||||
before_send(
|
||||
event, _filesystem_hint(OSError(errno.ENOSPC, "No space left on device"))
|
||||
)
|
||||
|
||||
assert event["fingerprint"] == expected_fingerprint
|
||||
|
||||
|
||||
def test_before_send_ignores_suppressed_context():
|
||||
"""`raise ... from None` hides the context, so it must not group the event."""
|
||||
try:
|
||||
try:
|
||||
raise OSError(errno.ENOSPC, "No space left on device")
|
||||
except OSError:
|
||||
raise RuntimeError("Error generating output directory") from None
|
||||
except RuntimeError as wrapper:
|
||||
event = {}
|
||||
before_send(event, _filesystem_hint(wrapper))
|
||||
|
||||
assert "fingerprint" not in event
|
||||
|
||||
|
||||
def test_errno_fingerprint_follows_implicit_context():
|
||||
"""An implicit `raise` during handling still exposes the original errno."""
|
||||
try:
|
||||
try:
|
||||
raise OSError(errno.EACCES, "Permission denied")
|
||||
except OSError:
|
||||
raise RuntimeError("Error generating output directory")
|
||||
except RuntimeError as wrapper:
|
||||
assert errno_fingerprint(wrapper) == "errno:EACCES"
|
||||
|
||||
|
||||
def test_before_send_does_not_fingerprint_oserror_without_errno():
|
||||
"""An OSError without errno has nothing to split the issue by."""
|
||||
event = {}
|
||||
|
||||
before_send(event, _filesystem_hint(OSError("no errno here")))
|
||||
|
||||
assert "fingerprint" not in event
|
||||
|
||||
|
||||
def test_errno_fingerprint_uses_raw_number_for_unknown_errno():
|
||||
"""Unmapped errno values still split the issue instead of being dropped."""
|
||||
assert errno_fingerprint(OSError(9999, "unknown")) == "errno:9999"
|
||||
|
||||
|
||||
def test_errno_fingerprint_stops_on_self_referencing_chain():
|
||||
"""A cyclic exception chain must not hang the fingerprint lookup."""
|
||||
first = ValueError("first")
|
||||
second = ValueError("second")
|
||||
first.__cause__ = second
|
||||
second.__cause__ = first
|
||||
|
||||
assert errno_fingerprint(first) is None
|
||||
|
||||
@@ -6,13 +6,10 @@ from api.v1.serializer_utils.integrations import (
|
||||
from api.v1.serializer_utils.providers import ProviderSecretField
|
||||
from api.v1.serializers import (
|
||||
ImageProviderSecret,
|
||||
IntegrationSerializer,
|
||||
IntegrationUpdateSerializer,
|
||||
KubernetesProviderSecret,
|
||||
OracleCloudProviderSecret,
|
||||
)
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.test import APIRequestFactory
|
||||
|
||||
|
||||
class TestS3ConfigSerializer:
|
||||
@@ -355,25 +352,3 @@ current-context: test-context
|
||||
|
||||
assert not serializer.is_valid()
|
||||
assert "kubeconfig_content" in serializer.errors
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestIntegrationSerializerJiraDomain:
|
||||
"""The serialized Jira `domain` must not reach the model instance."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"serializer_class", [IntegrationSerializer, IntegrationUpdateSerializer]
|
||||
)
|
||||
def test_to_representation_does_not_mutate_configuration(
|
||||
self, serializer_class, jira_integration_fixture
|
||||
):
|
||||
# `IntegrationUpdateSerializer` exposes a `HyperlinkedIdentityField`
|
||||
context = {"request": APIRequestFactory().get("/")}
|
||||
representation = serializer_class(
|
||||
jira_integration_fixture, context=context
|
||||
).data
|
||||
|
||||
assert representation["configuration"]["domain"] == "test"
|
||||
assert jira_integration_fixture.configuration == {
|
||||
"projects": {"TEST": "Test project"}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,10 @@ from allauth.account.models import EmailAddress
|
||||
from allauth.socialaccount.models import SocialAccount, SocialApp
|
||||
from api.attack_paths import (
|
||||
AttackPathsQueryDefinition,
|
||||
AttackPathsQueryOutcome,
|
||||
AttackPathsQueryParameterDefinition,
|
||||
)
|
||||
from api.compliance import get_compliance_frameworks
|
||||
from api.db_router import MainRouter
|
||||
from api.db_utils import rls_transaction
|
||||
from api.models import (
|
||||
AttackSurfaceOverview,
|
||||
ComplianceOverviewSummary,
|
||||
@@ -78,9 +76,8 @@ from conftest import (
|
||||
today_after_n_days,
|
||||
)
|
||||
from django.conf import settings
|
||||
from django.db import close_old_connections, connection, connections
|
||||
from django.db import close_old_connections, connection
|
||||
from django.db.models import Count
|
||||
from django.db.models.signals import pre_delete
|
||||
from django.http import JsonResponse
|
||||
from django.test import RequestFactory
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
@@ -520,50 +517,6 @@ class TestUserViewSet:
|
||||
assert error_field in response.json()["errors"][0]["source"]["pointer"]
|
||||
|
||||
|
||||
@pytest.mark.requires_test_admin_alias
|
||||
@pytest.mark.django_db(transaction=True, databases=["default", "admin"])
|
||||
class TestTenantDeletionTransactions:
|
||||
@patch("api.v1.views.delete_tenant_task.apply_async")
|
||||
def test_delete_rolls_back_memberships_when_user_cleanup_fails(
|
||||
self,
|
||||
delete_tenant_mock,
|
||||
authenticated_client,
|
||||
tenants_fixture,
|
||||
):
|
||||
assert connections["default"] is not connections["admin"]
|
||||
|
||||
_, tenant, _ = tenants_fixture
|
||||
exclusive_user = User.objects.create_user(
|
||||
name="exclusive user",
|
||||
password=TEST_PASSWORD,
|
||||
email="exclusive-user@example.com",
|
||||
)
|
||||
membership = Membership.objects.create(
|
||||
user=exclusive_user,
|
||||
tenant=tenant,
|
||||
role=Membership.RoleChoices.MEMBER,
|
||||
)
|
||||
|
||||
def fail_user_cleanup(*, instance, **kwargs):
|
||||
if instance.pk == exclusive_user.pk:
|
||||
raise RuntimeError("Simulated user cleanup failure.")
|
||||
|
||||
pre_delete.connect(fail_user_cleanup, sender=User)
|
||||
try:
|
||||
with (
|
||||
patch.object(MainRouter, "admin_db", "admin"),
|
||||
pytest.raises(RuntimeError, match=r"Simulated user cleanup failure\."),
|
||||
):
|
||||
authenticated_client.delete(
|
||||
reverse("tenant-detail", kwargs={"pk": tenant.id})
|
||||
)
|
||||
finally:
|
||||
pre_delete.disconnect(fail_user_cleanup, sender=User)
|
||||
|
||||
assert Membership.objects.using("admin").filter(pk=membership.pk).exists()
|
||||
delete_tenant_mock.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestTenantViewSet:
|
||||
@pytest.fixture
|
||||
@@ -5434,72 +5387,6 @@ class TestAttackPathsScanViewSet:
|
||||
assert payload[0]["attributes"]["name"] == "RDS inventory"
|
||||
assert payload[0]["attributes"]["parameters"][0]["name"] == "ip"
|
||||
|
||||
def test_attack_paths_queries_expose_outcome(
|
||||
self,
|
||||
authenticated_client,
|
||||
aws_provider,
|
||||
scans_fixture,
|
||||
create_attack_paths_scan,
|
||||
):
|
||||
provider = aws_provider
|
||||
attack_paths_scan = create_attack_paths_scan(
|
||||
provider,
|
||||
scan=scans_fixture[0],
|
||||
)
|
||||
|
||||
definitions = [
|
||||
AttackPathsQueryDefinition(
|
||||
id="aws-lambda-passrole",
|
||||
name="Lambda passrole",
|
||||
short_description="Pass a role to a new Lambda function.",
|
||||
description="Pass a role to a new Lambda function and run code as it.",
|
||||
provider=provider.provider,
|
||||
cypher="MATCH (n) RETURN n",
|
||||
outcome=AttackPathsQueryOutcome.CODE_EXECUTION,
|
||||
),
|
||||
AttackPathsQueryDefinition(
|
||||
id="aws-rds-inventory",
|
||||
name="RDS inventory",
|
||||
short_description="List account RDS assets.",
|
||||
description="List account RDS assets.",
|
||||
provider=provider.provider,
|
||||
cypher="MATCH (n) RETURN n",
|
||||
outcome=AttackPathsQueryOutcome.RESOURCE_INVENTORY,
|
||||
),
|
||||
AttackPathsQueryDefinition(
|
||||
id="aws-no-outcome",
|
||||
name="No outcome",
|
||||
short_description="A query without an outcome.",
|
||||
description="A query without an outcome.",
|
||||
provider=provider.provider,
|
||||
cypher="MATCH (n) RETURN n",
|
||||
),
|
||||
]
|
||||
|
||||
with patch("api.v1.views.get_queries_for_provider", return_value=definitions):
|
||||
response = authenticated_client.get(
|
||||
reverse(
|
||||
"attack-paths-scans-queries", kwargs={"pk": attack_paths_scan.id}
|
||||
)
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
outcomes = {
|
||||
item["id"]: item["attributes"]["outcome"]
|
||||
for item in response.json()["data"]
|
||||
}
|
||||
assert outcomes["aws-lambda-passrole"] == {
|
||||
"kind": "code_execution",
|
||||
"label": "Code execution",
|
||||
"partial": False,
|
||||
}
|
||||
assert outcomes["aws-rds-inventory"] == {
|
||||
"kind": "resource_inventory",
|
||||
"label": "Resource inventory",
|
||||
"partial": True,
|
||||
}
|
||||
assert outcomes["aws-no-outcome"] is None
|
||||
|
||||
def test_attack_paths_queries_returns_404_when_catalog_missing(
|
||||
self,
|
||||
authenticated_client,
|
||||
@@ -13611,73 +13498,6 @@ class TestIntegrationViewSet:
|
||||
f"Expected type '{expected_type}' not found in included data"
|
||||
)
|
||||
|
||||
# Serializing a Jira integration reads `configuration` to add the domain from the
|
||||
# credentials, and a sparse fieldset can leave that field out of the representation
|
||||
|
||||
def test_integrations_list_sparse_fields_without_configuration(
|
||||
self, authenticated_client, jira_integration_fixture
|
||||
):
|
||||
response = authenticated_client.get(
|
||||
reverse("integration-list"),
|
||||
{"fields[integrations]": "enabled,integration_type"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
attributes = response.json()["data"][0]["attributes"]
|
||||
assert sorted(attributes.keys()) == ["enabled", "integration_type"]
|
||||
|
||||
def test_integrations_retrieve_sparse_fields_without_configuration(
|
||||
self, authenticated_client, jira_integration_fixture
|
||||
):
|
||||
response = authenticated_client.get(
|
||||
reverse("integration-detail", kwargs={"pk": jira_integration_fixture.id}),
|
||||
{"fields[integrations]": "enabled,integration_type"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert "configuration" not in response.json()["data"]["attributes"]
|
||||
|
||||
def test_integrations_partial_update_sparse_fields_without_configuration(
|
||||
self, authenticated_client, jira_integration_fixture
|
||||
):
|
||||
data = {
|
||||
"data": {
|
||||
"type": "integrations",
|
||||
"id": str(jira_integration_fixture.id),
|
||||
"attributes": {"enabled": False},
|
||||
}
|
||||
}
|
||||
|
||||
url = reverse("integration-detail", kwargs={"pk": jira_integration_fixture.id})
|
||||
response = authenticated_client.patch(
|
||||
f"{url}?fields[integrations]=enabled,integration_type",
|
||||
data=json.dumps(data),
|
||||
content_type="application/vnd.api+json",
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert "configuration" not in response.json()["data"]["attributes"]
|
||||
with rls_transaction(str(jira_integration_fixture.tenant_id)):
|
||||
jira_integration_fixture.refresh_from_db()
|
||||
assert jira_integration_fixture.enabled is False
|
||||
# Omitting `configuration` from the fieldset must not rewrite it, and the
|
||||
# serialized `domain` must not leak into the stored value
|
||||
assert jira_integration_fixture.configuration == {
|
||||
"projects": {"TEST": "Test project"}
|
||||
}
|
||||
|
||||
def test_integrations_retrieve_jira_keeps_domain_in_configuration(
|
||||
self, authenticated_client, jira_integration_fixture
|
||||
):
|
||||
response = authenticated_client.get(
|
||||
reverse("integration-detail", kwargs={"pk": jira_integration_fixture.id})
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
configuration = response.json()["data"]["attributes"]["configuration"]
|
||||
assert configuration["domain"] == "test"
|
||||
assert configuration["projects"] == {"TEST": "Test project"}
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"integration_type, configuration, credentials",
|
||||
[
|
||||
@@ -14673,37 +14493,6 @@ class TestSAMLConfigurationViewSet:
|
||||
assert not SAMLConfiguration.objects.filter(id=config.id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestSAMLACSView:
|
||||
def test_get_is_not_allowed(self, client, saml_setup):
|
||||
response = client.get(
|
||||
reverse(
|
||||
"saml_acs",
|
||||
kwargs={"organization_slug": saml_setup["domain"]},
|
||||
)
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
|
||||
assert response.headers["Allow"] == "POST"
|
||||
assert "saml-acs-session" not in response.cookies
|
||||
|
||||
def test_post_is_forwarded_to_allauth(self, client, saml_setup):
|
||||
response = client.post(
|
||||
reverse(
|
||||
"saml_acs",
|
||||
kwargs={"organization_slug": saml_setup["domain"]},
|
||||
),
|
||||
data={"SAMLResponse": "test-saml-response"},
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_302_FOUND
|
||||
assert response.url == reverse(
|
||||
"saml_finish_acs",
|
||||
kwargs={"organization_slug": saml_setup["domain"]},
|
||||
)
|
||||
assert "saml-acs-session" in response.cookies
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestTenantFinishACSView:
|
||||
def test_dispatch_skips_if_user_not_authenticated(self, monkeypatch):
|
||||
|
||||
@@ -329,15 +329,6 @@ class TokenSwitchTenantSerializer(BaseSerializerV1):
|
||||
# Users
|
||||
|
||||
|
||||
class ActiveMembershipRelatedField(SerializerMethodResourceRelatedField):
|
||||
def to_representation(self, value):
|
||||
representation = super().to_representation(value)
|
||||
representation["meta"] = {
|
||||
"active": str(value.tenant_id) == str(self.context["request"].tenant_id),
|
||||
}
|
||||
return representation
|
||||
|
||||
|
||||
class UserSerializer(BaseModelSerializerV1):
|
||||
"""
|
||||
Serializer for the User model.
|
||||
@@ -399,12 +390,6 @@ class UserSerializer(BaseModelSerializerV1):
|
||||
)
|
||||
|
||||
|
||||
class UserMeSerializer(UserSerializer):
|
||||
memberships = ActiveMembershipRelatedField(
|
||||
many=True, read_only=True, source="memberships", method_name="get_memberships"
|
||||
)
|
||||
|
||||
|
||||
class UserIncludeSerializer(UserSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
@@ -1330,28 +1315,6 @@ class AttackPathsQuerySerializer(BaseSerializerV1):
|
||||
attribution = AttackPathsQueryAttributionSerializer(allow_null=True, required=False)
|
||||
provider = serializers.CharField()
|
||||
parameters = AttackPathsQueryParameterSerializer(many=True)
|
||||
# The terminal impact the query leads to (e.g. {"kind": "code_execution",
|
||||
# "label": "Code execution"}), or null if the query has none. The UI renders
|
||||
# this as the graph's terminal outcome node.
|
||||
outcome = serializers.SerializerMethodField()
|
||||
|
||||
@extend_schema_field(
|
||||
{
|
||||
"type": "object",
|
||||
"nullable": True,
|
||||
"properties": {
|
||||
"kind": {"type": "string"},
|
||||
"label": {"type": "string"},
|
||||
"partial": {"type": "boolean"},
|
||||
},
|
||||
}
|
||||
)
|
||||
def get_outcome(self, definition):
|
||||
outcome = getattr(definition, "outcome", None)
|
||||
if outcome is None:
|
||||
return None
|
||||
meta = outcome.value
|
||||
return {"kind": meta.kind, "label": meta.label, "partial": meta.partial}
|
||||
|
||||
class JSONAPIMeta:
|
||||
resource_name = "attack-paths-queries"
|
||||
@@ -3043,15 +3006,10 @@ class IntegrationSerializer(IntegrationProviderVisibilityMixin, RLSSerializer):
|
||||
representation = self.hide_restricted_providers(
|
||||
super().to_representation(instance)
|
||||
)
|
||||
# `configuration` is missing when the request asks for a subset of the fields
|
||||
if (
|
||||
instance.integration_type == Integration.IntegrationChoices.JIRA
|
||||
and "configuration" in representation
|
||||
):
|
||||
representation["configuration"] = {
|
||||
**representation["configuration"],
|
||||
"domain": instance.credentials.get("domain"),
|
||||
}
|
||||
if instance.integration_type == Integration.IntegrationChoices.JIRA:
|
||||
representation["configuration"].update(
|
||||
{"domain": instance.credentials.get("domain")}
|
||||
)
|
||||
return representation
|
||||
|
||||
|
||||
@@ -3185,16 +3143,11 @@ class IntegrationUpdateSerializer(
|
||||
representation = self.hide_restricted_providers(
|
||||
super().to_representation(instance)
|
||||
)
|
||||
# Ensure JIRA integrations show updated domain in configuration from credentials.
|
||||
# `configuration` is missing when the request asks for a subset of the fields
|
||||
if (
|
||||
instance.integration_type == Integration.IntegrationChoices.JIRA
|
||||
and "configuration" in representation
|
||||
):
|
||||
representation["configuration"] = {
|
||||
**representation["configuration"],
|
||||
"domain": instance.credentials.get("domain"),
|
||||
}
|
||||
# Ensure JIRA integrations show updated domain in configuration from credentials
|
||||
if instance.integration_type == Integration.IntegrationChoices.JIRA:
|
||||
representation["configuration"].update(
|
||||
{"domain": instance.credentials.get("domain")}
|
||||
)
|
||||
return representation
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ from api.v1.views import (
|
||||
from django.http import JsonResponse
|
||||
from django.urls import include, path
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST
|
||||
from drf_spectacular.views import SpectacularRedocView
|
||||
from rest_framework_nested import routers
|
||||
|
||||
@@ -195,7 +194,7 @@ urlpatterns = [
|
||||
),
|
||||
path(
|
||||
"accounts/saml/<organization_slug>/acs/",
|
||||
require_POST(ACSView.as_view()),
|
||||
ACSView.as_view(),
|
||||
name="saml_acs",
|
||||
),
|
||||
path(
|
||||
|
||||
@@ -238,7 +238,6 @@ from api.v1.serializers import (
|
||||
TokenSocialLoginSerializer,
|
||||
TokenSwitchTenantSerializer,
|
||||
UserCreateSerializer,
|
||||
UserMeSerializer,
|
||||
UserRoleRelationshipSerializer,
|
||||
UserSerializer,
|
||||
UserUpdateSerializer,
|
||||
@@ -1114,8 +1113,6 @@ class UserViewSet(BaseUserViewset):
|
||||
return UserCreateSerializer
|
||||
elif self.action == "partial_update":
|
||||
return UserUpdateSerializer
|
||||
elif self.action == "me":
|
||||
return UserMeSerializer
|
||||
else:
|
||||
return UserSerializer
|
||||
|
||||
@@ -1133,7 +1130,7 @@ class UserViewSet(BaseUserViewset):
|
||||
@action(detail=False, methods=["get"], url_name="me")
|
||||
def me(self, request):
|
||||
user = self.request.user
|
||||
serializer = self.get_serializer(user)
|
||||
serializer = UserSerializer(user, context=self.get_serializer_context())
|
||||
return Response(
|
||||
data=serializer.data,
|
||||
status=status.HTTP_200_OK,
|
||||
@@ -1445,7 +1442,7 @@ class TenantViewSet(BaseTenantViewset):
|
||||
if not membership or membership.role != Membership.RoleChoices.OWNER:
|
||||
raise PermissionDenied("Only owners can delete a tenant.")
|
||||
|
||||
with transaction.atomic(using=MainRouter.admin_db):
|
||||
with transaction.atomic():
|
||||
# Collect user IDs from this tenant's memberships before deleting them
|
||||
tenant_user_ids = set(
|
||||
Membership.objects.using(MainRouter.admin_db)
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
from errno import errorcode
|
||||
|
||||
import sentry_sdk
|
||||
from config.env import env
|
||||
|
||||
# How many links of the __cause__/__context__ chain are inspected when looking
|
||||
# for the OSError that actually caused the event.
|
||||
MAX_EXCEPTION_CHAIN_DEPTH = 10
|
||||
|
||||
# LogRecord attribute describing what kind of failure the record reports, set by
|
||||
# the caller through `logger.exception(..., extra={"error_category": ...})`.
|
||||
ERROR_CATEGORY_ATTRIBUTE = "error_category"
|
||||
|
||||
# Category of records whose events are grouped by the errno of the underlying
|
||||
# OSError. Only records that declare it opt into the errno fingerprint.
|
||||
FILESYSTEM_ERROR_CATEGORY = "filesystem"
|
||||
|
||||
IGNORED_EXCEPTIONS = [
|
||||
# Provider is not connected due to credentials errors
|
||||
"is not connected",
|
||||
@@ -94,38 +80,6 @@ IGNORED_EXCEPTIONS = [
|
||||
]
|
||||
|
||||
|
||||
def errno_fingerprint(exception):
|
||||
"""
|
||||
Return an errno-based fingerprint suffix for OSError-like exceptions.
|
||||
|
||||
Filesystem failures such as ENOSPC (disk full), ENOENT (missing mount point)
|
||||
or EACCES (wrong permissions) are all OSError raised from the same call
|
||||
site, so Sentry's default grouping merges them into a single issue even
|
||||
when the exception is attached to the event. Appending the errno keeps the
|
||||
default grouping and splits the issue per failure cause.
|
||||
|
||||
Only the part of the chain Sentry itself displays is inspected: a
|
||||
`raise ... from None` sets __suppress_context__, so the implicit
|
||||
__context__ is dropped from the event and must not group it either.
|
||||
|
||||
Returns None when no OSError with an errno is found in the exception chain.
|
||||
"""
|
||||
seen = set()
|
||||
for _ in range(MAX_EXCEPTION_CHAIN_DEPTH):
|
||||
if exception is None or id(exception) in seen:
|
||||
break
|
||||
seen.add(id(exception))
|
||||
if isinstance(exception, OSError) and exception.errno is not None:
|
||||
return f"errno:{errorcode.get(exception.errno, exception.errno)}"
|
||||
if exception.__cause__ is not None:
|
||||
exception = exception.__cause__
|
||||
elif exception.__suppress_context__:
|
||||
break
|
||||
else:
|
||||
exception = exception.__context__
|
||||
return None
|
||||
|
||||
|
||||
def before_send(event, hint):
|
||||
"""
|
||||
before_send handles the Sentry events in order to send them or not
|
||||
@@ -161,28 +115,10 @@ def before_send(event, hint):
|
||||
|
||||
# Ignore exceptions with the ignored_exceptions
|
||||
if "exc_info" in hint and hint["exc_info"]:
|
||||
exception = hint["exc_info"][1]
|
||||
exc_value = str(exception)
|
||||
exc_value = str(hint["exc_info"][1])
|
||||
if any(ignored in exc_value for ignored in IGNORED_EXCEPTIONS):
|
||||
return None # Explicitly return None to drop the event
|
||||
|
||||
# Split filesystem issues per errno instead of grouping every failure raised
|
||||
# from the same call site under a single issue. Only records that declare
|
||||
# themselves as filesystem failures opt in, and a fingerprint already set by
|
||||
# a scope or an integration always wins.
|
||||
log_record = hint.get("log_record")
|
||||
exc_info = hint.get("exc_info")
|
||||
if (
|
||||
log_record is not None
|
||||
and exc_info
|
||||
and getattr(log_record, ERROR_CATEGORY_ATTRIBUTE, None)
|
||||
== FILESYSTEM_ERROR_CATEGORY
|
||||
and "fingerprint" not in event
|
||||
):
|
||||
fingerprint_suffix = errno_fingerprint(exc_info[1])
|
||||
if fingerprint_suffix:
|
||||
event["fingerprint"] = ["{{ default }}", fingerprint_suffix]
|
||||
|
||||
return event
|
||||
|
||||
|
||||
|
||||
@@ -1450,26 +1450,6 @@ def integrations_fixture(aws_provider_pair):
|
||||
return integration1, integration2
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def jira_integration_fixture(tenants_fixture):
|
||||
# Jira is a tenant-wide integration: it is not attached to any provider, and its
|
||||
# `domain` is read from the credentials when the integration is serialized
|
||||
tenant_id = tenants_fixture[0].id
|
||||
with rls_transaction(str(tenant_id)):
|
||||
return Integration.objects.create(
|
||||
tenant_id=tenant_id,
|
||||
enabled=True,
|
||||
connected=True,
|
||||
integration_type=Integration.IntegrationChoices.JIRA,
|
||||
configuration={"projects": {"TEST": "Test project"}},
|
||||
credentials={
|
||||
"domain": "test",
|
||||
"user_mail": "a@b.com",
|
||||
"api_token": "token",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def backfill_scan_metadata_fixture(scans_fixture, findings_fixture):
|
||||
for scan_instance in scans_fixture:
|
||||
|
||||
@@ -13,7 +13,6 @@ from api.db_utils import rls_transaction
|
||||
from api.models import Provider, Scan, ScanSummary, StateChoices, ThreatScoreSnapshot
|
||||
from celery.utils.log import get_task_logger
|
||||
from config.django.base import DJANGO_TMP_OUTPUT_DIRECTORY
|
||||
from config.settings.sentry import ERROR_CATEGORY_ATTRIBUTE, FILESYSTEM_ERROR_CATEGORY
|
||||
from prowler.lib.check.compliance_models import (
|
||||
Compliance,
|
||||
get_bulk_compliance_frameworks_universal,
|
||||
@@ -961,15 +960,7 @@ def generate_compliance_reports(
|
||||
first_output_path = next(iter(output_paths.values()))
|
||||
out_dir = str(Path(first_output_path).parent.parent)
|
||||
except Exception as e:
|
||||
# logger.exception attaches the exception (and its traceback) to the
|
||||
# Sentry event and the filesystem category opts that event into the
|
||||
# errno fingerprint, so ENOSPC, ENOENT and EACCES raised from this same
|
||||
# call site land on separate issues.
|
||||
logger.exception(
|
||||
"Error generating output directory: %s",
|
||||
e,
|
||||
extra={ERROR_CATEGORY_ATTRIBUTE: FILESYSTEM_ERROR_CATEGORY},
|
||||
)
|
||||
logger.error("Error generating output directory: %s", e)
|
||||
error_dict = {"error": str(e), "upload": False, "path": ""}
|
||||
if generate_threatscore:
|
||||
results["threatscore"] = error_dict.copy()
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
@@ -16,11 +14,6 @@ from api.models import (
|
||||
StateChoices,
|
||||
StatusChoices,
|
||||
)
|
||||
from config.settings.sentry import (
|
||||
ERROR_CATEGORY_ATTRIBUTE,
|
||||
FILESYSTEM_ERROR_CATEGORY,
|
||||
before_send,
|
||||
)
|
||||
from prowler.lib.check.models import Severity
|
||||
from reportlab.lib import colors
|
||||
from tasks.jobs.report import (
|
||||
@@ -1683,78 +1676,6 @@ class TestGenerateComplianceReportsCIS:
|
||||
assert result["cis"]["upload"] is False
|
||||
assert result["cis"]["error"] == "dir boom"
|
||||
|
||||
@patch("tasks.jobs.report._aggregate_requirement_statistics_from_database")
|
||||
@patch("tasks.jobs.report._generate_compliance_output_directory")
|
||||
@patch("tasks.jobs.report.Compliance.get_bulk")
|
||||
def test_output_directory_failures_are_grouped_per_errno(
|
||||
self,
|
||||
mock_get_bulk,
|
||||
mock_generate_output_dir,
|
||||
mock_stats,
|
||||
monkeypatch,
|
||||
caplog,
|
||||
tenants_fixture,
|
||||
scans_fixture,
|
||||
aws_provider,
|
||||
):
|
||||
"""A full disk and a missing mount point must not share a Sentry issue.
|
||||
|
||||
Both are OSError raised from the same ``os.makedirs`` call, so they only
|
||||
stay apart if the exception reaches ``before_send``, which fingerprints
|
||||
it by errno.
|
||||
"""
|
||||
tenant = tenants_fixture[0]
|
||||
scan = scans_fixture[0]
|
||||
provider = aws_provider
|
||||
|
||||
self._force_scan_has_findings(monkeypatch)
|
||||
mock_stats.return_value = {}
|
||||
mock_get_bulk.return_value = {"cis_5.0_aws": Mock()}
|
||||
|
||||
fingerprints = []
|
||||
for error_number, message in (
|
||||
(errno.ENOSPC, "No space left on device: '/tmp/prowler_api_output'"),
|
||||
(errno.ENOENT, "No such file or directory: '/mnt/output'"),
|
||||
):
|
||||
mock_generate_output_dir.side_effect = OSError(error_number, message)
|
||||
caplog.clear()
|
||||
|
||||
with caplog.at_level(logging.ERROR, logger="tasks.jobs.report"):
|
||||
generate_compliance_reports(
|
||||
tenant_id=str(tenant.id),
|
||||
scan_id=str(scan.id),
|
||||
provider_id=str(provider.id),
|
||||
generate_threatscore=False,
|
||||
generate_ens=False,
|
||||
generate_nis2=False,
|
||||
generate_csa=False,
|
||||
generate_cis=True,
|
||||
)
|
||||
|
||||
record = next(
|
||||
record
|
||||
for record in caplog.records
|
||||
if "Error generating output directory" in record.getMessage()
|
||||
)
|
||||
# Without exc_info the Sentry event carries no exception at all and
|
||||
# nothing can tell the two failures apart.
|
||||
assert record.exc_info is not None
|
||||
# The category is what scopes the errno fingerprint to this record.
|
||||
assert (
|
||||
getattr(record, ERROR_CATEGORY_ATTRIBUTE, None)
|
||||
== FILESYSTEM_ERROR_CATEGORY
|
||||
)
|
||||
|
||||
# Same hint the Sentry logging integration builds for this record.
|
||||
event = {}
|
||||
before_send(event, {"log_record": record, "exc_info": record.exc_info})
|
||||
fingerprints.append(event["fingerprint"])
|
||||
|
||||
assert fingerprints == [
|
||||
["{{ default }}", "errno:ENOSPC"],
|
||||
["{{ default }}", "errno:ENOENT"],
|
||||
]
|
||||
|
||||
|
||||
class TestPickLatestCisVariant:
|
||||
"""Unit tests for `_pick_latest_cis_variant` helper."""
|
||||
|
||||
Generated
+172
-418
@@ -16,7 +16,7 @@ constraints = [
|
||||
{ name = "aiobotocore", specifier = "==2.25.1" },
|
||||
{ name = "aiofiles", specifier = "==24.1.0" },
|
||||
{ name = "aiohappyeyeballs", specifier = "==2.6.1" },
|
||||
{ name = "aiohttp", specifier = "==3.14.3" },
|
||||
{ name = "aiohttp", specifier = "==3.14.0" },
|
||||
{ name = "aioitertools", specifier = "==0.13.0" },
|
||||
{ name = "aiosignal", specifier = "==1.4.0" },
|
||||
{ name = "alibabacloud-actiontrail20200706", specifier = "==2.4.1" },
|
||||
@@ -45,7 +45,7 @@ constraints = [
|
||||
{ name = "alibabacloud-sls20201230", specifier = "==5.9.0" },
|
||||
{ name = "alibabacloud-sts20150401", specifier = "==1.1.6" },
|
||||
{ name = "alibabacloud-tea", specifier = "==0.4.3" },
|
||||
{ name = "alibabacloud-tea-openapi", specifier = "==0.4.5" },
|
||||
{ name = "alibabacloud-tea-openapi", specifier = "==0.4.4" },
|
||||
{ name = "alibabacloud-tea-util", specifier = "==0.3.14" },
|
||||
{ name = "alibabacloud-tea-xml", specifier = "==0.0.3" },
|
||||
{ name = "alibabacloud-vpc20160428", specifier = "==6.13.0" },
|
||||
@@ -127,9 +127,9 @@ constraints = [
|
||||
{ name = "coverage", specifier = "==7.5.4" },
|
||||
{ name = "cron-descriptor", specifier = "==1.4.5" },
|
||||
{ name = "crowdstrike-falconpy", specifier = "==1.6.0" },
|
||||
{ name = "cryptography", specifier = "==50.0.0" },
|
||||
{ name = "cryptography", specifier = "==46.0.7" },
|
||||
{ name = "cycler", specifier = "==0.12.1" },
|
||||
{ name = "darabonba-core", specifier = "==1.0.8" },
|
||||
{ name = "darabonba-core", specifier = "==1.0.5" },
|
||||
{ name = "dash", specifier = "==3.1.1" },
|
||||
{ name = "dash-bootstrap-components", specifier = "==2.0.3" },
|
||||
{ name = "debugpy", specifier = "==1.8.20" },
|
||||
@@ -194,7 +194,7 @@ constraints = [
|
||||
{ name = "h2", specifier = "==4.3.0" },
|
||||
{ name = "hpack", specifier = "==4.1.0" },
|
||||
{ name = "httpcore", specifier = "==1.0.9" },
|
||||
{ name = "httplib2", specifier = "==0.32.0" },
|
||||
{ name = "httplib2", specifier = "==0.31.2" },
|
||||
{ name = "httpx", specifier = "==0.28.1" },
|
||||
{ name = "humanfriendly", specifier = "==10.0" },
|
||||
{ name = "hyperframe", specifier = "==6.1.0" },
|
||||
@@ -231,13 +231,13 @@ constraints = [
|
||||
{ name = "matplotlib", specifier = "==3.10.8" },
|
||||
{ name = "mccabe", specifier = "==0.7.0" },
|
||||
{ name = "mdurl", specifier = "==0.1.2" },
|
||||
{ name = "microsoft-kiota-abstractions", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-authentication-azure", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-http", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-serialization-form", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-serialization-json", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-serialization-multipart", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-serialization-text", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-abstractions", specifier = "==1.9.9" },
|
||||
{ name = "microsoft-kiota-authentication-azure", specifier = "==1.9.9" },
|
||||
{ name = "microsoft-kiota-http", specifier = "==1.9.9" },
|
||||
{ name = "microsoft-kiota-serialization-form", specifier = "==1.9.9" },
|
||||
{ name = "microsoft-kiota-serialization-json", specifier = "==1.9.9" },
|
||||
{ name = "microsoft-kiota-serialization-multipart", specifier = "==1.9.9" },
|
||||
{ name = "microsoft-kiota-serialization-text", specifier = "==1.9.9" },
|
||||
{ name = "microsoft-security-utilities-secret-masker", specifier = "==1.0.0b4" },
|
||||
{ name = "msal", specifier = "==1.35.0b1" },
|
||||
{ name = "msal-extensions", specifier = "==1.2.0" },
|
||||
@@ -254,7 +254,7 @@ constraints = [
|
||||
{ name = "nltk", specifier = "==3.9.4" },
|
||||
{ name = "numpy", specifier = "==2.2.6" },
|
||||
{ name = "oauthlib", specifier = "==3.3.1" },
|
||||
{ name = "oci", specifier = "==2.183.0" },
|
||||
{ name = "oci", specifier = "==2.169.0" },
|
||||
{ name = "openai", specifier = "==1.109.1" },
|
||||
{ name = "openstacksdk", specifier = "==4.2.0" },
|
||||
{ name = "opentelemetry-api", specifier = "==1.39.1" },
|
||||
@@ -266,7 +266,7 @@ constraints = [
|
||||
{ name = "pagerduty", specifier = "==6.1.0" },
|
||||
{ name = "pandas", specifier = "==2.2.3" },
|
||||
{ name = "pbr", specifier = "==7.0.3" },
|
||||
{ name = "pillow", specifier = "==12.3.0" },
|
||||
{ name = "pillow", specifier = "==12.2.0" },
|
||||
{ name = "pkginfo", specifier = "==1.12.1.2" },
|
||||
{ name = "platformdirs", specifier = "==4.5.1" },
|
||||
{ name = "plotly", specifier = "==6.5.2" },
|
||||
@@ -282,8 +282,8 @@ constraints = [
|
||||
{ name = "psycopg2-binary", specifier = "==2.9.9" },
|
||||
{ name = "py-deviceid", specifier = "==0.1.1" },
|
||||
{ name = "py-iam-expand", specifier = "==0.3.0" },
|
||||
{ name = "py-ocsf-models", specifier = "==0.10.0" },
|
||||
{ name = "pyasn1", specifier = "==0.6.4" },
|
||||
{ name = "py-ocsf-models", specifier = "==0.8.1" },
|
||||
{ name = "pyasn1", specifier = "==0.6.3" },
|
||||
{ name = "pyasn1-modules", specifier = "==0.4.2" },
|
||||
{ name = "pycodestyle", specifier = "==2.14.0" },
|
||||
{ name = "pycparser", specifier = "==3.0" },
|
||||
@@ -295,7 +295,7 @@ constraints = [
|
||||
{ name = "pylint", specifier = "==3.2.5" },
|
||||
{ name = "pymsalruntime", specifier = "==0.18.1" },
|
||||
{ name = "pynacl", specifier = "==1.6.2" },
|
||||
{ name = "pyopenssl", specifier = "==26.2.0" },
|
||||
{ name = "pyopenssl", specifier = "==26.0.0" },
|
||||
{ name = "pyparsing", specifier = "==3.3.2" },
|
||||
{ name = "pyreadline3", specifier = "==3.5.4" },
|
||||
{ name = "pysocks", specifier = "==1.7.1" },
|
||||
@@ -364,7 +364,7 @@ constraints = [
|
||||
{ name = "wcwidth", specifier = "==0.5.3" },
|
||||
{ name = "websocket-client", specifier = "==1.9.0" },
|
||||
{ name = "werkzeug", specifier = "==3.1.7" },
|
||||
{ name = "workos", specifier = "==8.3.0" },
|
||||
{ name = "workos", specifier = "==6.0.8" },
|
||||
{ name = "wrapt", specifier = "==1.17.3" },
|
||||
{ name = "xlsxwriter", specifier = "==3.2.9" },
|
||||
{ name = "xmlsec", specifier = "==1.3.17" },
|
||||
@@ -377,15 +377,8 @@ constraints = [
|
||||
]
|
||||
overrides = [
|
||||
{ name = "azure-mgmt-containerservice", specifier = "==34.1.0" },
|
||||
{ name = "cryptography", specifier = "==50.0.0" },
|
||||
{ name = "dulwich", specifier = "==1.2.5" },
|
||||
{ name = "microsoft-kiota-abstractions", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-authentication-azure", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-http", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-serialization-form", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-serialization-json", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-serialization-multipart", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-serialization-text", specifier = "==1.9.10" },
|
||||
{ name = "microsoft-kiota-abstractions", specifier = "==1.9.9" },
|
||||
{ name = "okta", specifier = "==3.4.2" },
|
||||
{ name = "pyjwt", extras = ["crypto"], specifier = "==2.13.0" },
|
||||
]
|
||||
@@ -479,7 +472,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
version = "3.14.3"
|
||||
version = "3.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohappyeyeballs" },
|
||||
@@ -491,44 +484,44 @@ dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "yarl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/ab/93ce242f899b68c51b0578c027aafa791ab3614cb9345fa5d37b5f5c8e3e/aiohttp-3.14.0.tar.gz", hash = "sha256:2882de819734c715fd1b9c11c97e09fa020d14438203d1d354d8ed1702791c9b", size = 7940674, upload-time = "2026-06-01T19:41:02.763Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/5c/b3e4ff8ad43a8afef9602c5e90285936da1beaea8b029016b793891f03c3/aiohttp-3.14.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3", size = 764250, upload-time = "2026-07-23T01:52:48.525Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/da/f1b384465e51449d844056b75070461da03a9a23e6c1747003695bf4172a/aiohttp-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a", size = 516281, upload-time = "2026-07-23T01:52:51.047Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/3f/01264f820ee2e3712a827892b1cd6ff80f3300c1fcbffbb45714a915d47a/aiohttp-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8", size = 514742, upload-time = "2026-07-23T01:52:53.779Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/8d/a71c6f2db52ac1ed142b133f7feddaa6b70539c3f4de24d7e226c95b794c/aiohttp-3.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239", size = 1780613, upload-time = "2026-07-23T01:52:56.948Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/11/3dd9b3fb3a170f6ec9011b5291d876a6fab4086714c9e158600edf01b4fd/aiohttp-3.14.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f", size = 1737688, upload-time = "2026-07-23T01:52:59.294Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/3e/834c26918be7d88068822b40e0db30fca50b5f4fe79104aa16a93f1d74e6/aiohttp-3.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06", size = 1845742, upload-time = "2026-07-23T01:53:01.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/c9/49ab8572df7d66bc13d11e31f781292badb04180dd87ba98733066c6aed7/aiohttp-3.14.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929", size = 1928412, upload-time = "2026-07-23T01:53:04.018Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/b9/2b8f0c0ce09c87a1daf80fd483431b56b1435d3f62789bc86f572e1245de/aiohttp-3.14.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db", size = 1786220, upload-time = "2026-07-23T01:53:06.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/00/9c45f81de11710460edfa1dc81317b6e882703b160926c879a9d20da9fcc/aiohttp-3.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce", size = 1637231, upload-time = "2026-07-23T01:53:10.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/ce/967d628e910756f3539c6107cb7844a1b69440dcb3029a5ee7871b09ab63/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c", size = 1753161, upload-time = "2026-07-23T01:53:13.817Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/b2/0c3d4114f0aee4f580f5b3b4eb71b24d7a23b834ea506a4dfebe76513f35/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15", size = 1756356, upload-time = "2026-07-23T01:53:16.211Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/5d/99e7d91c82f1399d1ae2a854e080bd1493fbc31e5e959dbc4ec33dac3bec/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c", size = 1819846, upload-time = "2026-07-23T01:53:18.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/05/d5e1cb6480eeffd3f901d40a2c5e2d1e7effdc797837da3b490272699f13/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae", size = 1628531, upload-time = "2026-07-23T01:53:23.86Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/90/b934682bcaefae18a9e04f3dff5b68522ba810906358ae5029b68110ea3b/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910", size = 1832712, upload-time = "2026-07-23T01:53:27.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/df/6061679faaf81fac746e7307c7adb71e858071a5d34c27583afefc64f543/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7", size = 1775014, upload-time = "2026-07-23T01:53:30.223Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/1d/f854878bbc69b88faefe924b619a34a6f59ec05fd387c77690667eaa75eb/aiohttp-3.14.3-cp311-cp311-win32.whl", hash = "sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa", size = 456006, upload-time = "2026-07-23T01:53:34.97Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/0c/2af9d1674baccd1dbd47282a93d660a22e57ef6167c856deb24b4214fbab/aiohttp-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d", size = 481069, upload-time = "2026-07-23T01:53:39.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/76/88401ff3fc95e85c5fc38d588f36f55e61ecb64343b2bc8d69326f453cc0/aiohttp-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39", size = 453021, upload-time = "2026-07-23T01:53:43.749Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/d4/eb96299230e20acf2efae207cb8d69051f1f68e357e5ea5e479bf6fb097a/aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5", size = 754690, upload-time = "2026-07-23T01:53:47.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/11/e7a70a209eb9a067c0d3212b518a0134e3484f5178c7533878b6b514d469/aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228", size = 509484, upload-time = "2026-07-23T01:53:51.159Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/07/4bbc222cc8dbe31d4c3e8a5baad2286e4d42026ac0c570027b89afce6344/aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee", size = 511949, upload-time = "2026-07-23T01:53:55.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/b9/42e74c46b7b7c794b995bbc1f573fb48950c38b19d8600c62a6804ee2d67/aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a", size = 1765282, upload-time = "2026-07-23T01:53:59.662Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/ed/62bc4d74363ad346d518e0720363a949f63e2e23439a79eb5813d4d29bb3/aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b", size = 1741511, upload-time = "2026-07-23T01:54:04.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/9f/181e8a8bc79e47d13c7fc4540bd7a3b729d9505609c61f392a8dd2fbfe55/aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529", size = 1810680, upload-time = "2026-07-23T01:54:09.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/9a/dec94d6ad694552fe3424e3f1928d7a606a5d9d9433a04e7ecdd9d38ae7f/aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787", size = 1905646, upload-time = "2026-07-23T01:54:13.475Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/b7/7cd31f29d6055bd711ae6e669367fba6f5ae9de463910a793e30556a8db7/aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42", size = 1792122, upload-time = "2026-07-23T01:54:15.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/73/10b1ef93afa61f4963c746257b70ced619cf31a4798671de5fdb2608501d/aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b", size = 1591127, upload-time = "2026-07-23T01:54:19.489Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/ed/3b203fa6de1b338c14acdc06bf6ca9b043b7944f005966958c2ced932cde/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043", size = 1725210, upload-time = "2026-07-23T01:54:24.129Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/b7/1c2aab8c706436dcc28598452488ac9cd7c409da815237c28c27d58993e6/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427", size = 1764848, upload-time = "2026-07-23T01:54:27.973Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/50/94c28f08b131c4bf10984ea2c7a536c9920608bb2d6e7f95642c30cc87b7/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d", size = 1777102, upload-time = "2026-07-23T01:54:31.775Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/d4/e7d09ba7d345fb2d74440fd2fa033c5e079fac05552927705986f41a364f/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0", size = 1580205, upload-time = "2026-07-23T01:54:34.518Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/84/072a91d68e1e1eb587985b54baab94221277f877e8ef274fc213a0ceae28/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d", size = 1797219, upload-time = "2026-07-23T01:54:36.995Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/eb/aad34e897e668424d6e995da5dff8a4a09af93363d3392488772957a63aa/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19", size = 1768629, upload-time = "2026-07-23T01:54:40.103Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/2b/6bb88ddba0fecd9122aa3ebcad25996cf6c083a4a7040dbb3a4f97972af6/aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559", size = 451481, upload-time = "2026-07-23T01:54:42.547Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/9b/f2f8f108da17ecef2cc3efc424e8b7ad3782b1a8360f7b8eae8ced84f6ea/aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a", size = 476845, upload-time = "2026-07-23T01:54:44.853Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/44/28dac80a8941b604f4da10ce21097614ca1bf905ce93dca28d8d7de9c1e7/aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c", size = 448050, upload-time = "2026-07-23T01:54:47.087Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/47/7727bfe8db93f8835a001bd4359d8480cc68d1259b8bce334668f8be97bd/aiohttp-3.14.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:54bf3522d6f7351e55f89a62d5c2bf138ad557b031670266c5df604ae88e0b5a", size = 759147, upload-time = "2026-06-01T19:37:12.918Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/f2/cd3fedff6fade73d71df9ec908c210cec518ef90fd00289250684b90aecf/aiohttp-3.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0746d9fb0ac4fdef643a84494efe3f06d50335dd8c7a530228b86448aae0a803", size = 513705, upload-time = "2026-06-01T19:37:14.633Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/fe/49746b6b610144a06323bebd8e1211a390310d8c69b98dd6d52df341bc3e/aiohttp-3.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f3a96b6d39a4872222beee72e1df41d2ff886ae96152cf3e757ef8c5673ef0e", size = 509627, upload-time = "2026-06-01T19:37:16.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/3f/28f2f6cf3d5c0e7b01b27140d0e7873fd11fb341169ad3ce78ad04aba628/aiohttp-3.14.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d336820adbb914debbc90a1d8c1bfc4bea55996aecf64866a989d35d1f9fd903", size = 1769293, upload-time = "2026-06-01T19:37:18.067Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/6f/2e5f1b525d5474b12b3c60abf733a755845f3bceff21542081ada515f837/aiohttp-3.14.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:71b2604c9bfc1b115547d63a094d5244b3f02799833513a99a68aaa7b167c4cb", size = 1732363, upload-time = "2026-06-01T19:37:20.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/ce/596120faa85ca7b19cd061e3f2f3be23aa8f11a0aedf9191db9e0da1bd76/aiohttp-3.14.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:610d68800435903e303ca0542b9d3e4eb72a12ff33a6d471a070c1d81eebd3c2", size = 1840375, upload-time = "2026-06-01T19:37:22.104Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/3c/a7ffe05a757a4a7867643da69357ec41f506879fbd1b231d2ed90af246b2/aiohttp-3.14.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:514db9a79337068981ee2137310283a07b4b885c584991097a91a4da419bcb81", size = 1921484, upload-time = "2026-06-01T19:37:24.068Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/fa/2c861170bbd4a491de93a69e081db1d971092569e0d593a98ef62c384dc1/aiohttp-3.14.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c452d17eeb95d563fc8b936f3050301dbd1d268126c4632d8b70ede9696202ee", size = 1774153, upload-time = "2026-06-01T19:37:26.256Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/da/1d2f5a165f47ec9b1f69d37b8b977fdc4d501aa72ffb7930db27bb9e49ea/aiohttp-3.14.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ed94a81506e3d1bdbad5108f497a58f2a2354aedb4ca314d5326f07d1fd1ac2d", size = 1632569, upload-time = "2026-06-01T19:37:28.192Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/1d/7a6e295c4257252f70f69e90864fdad74b6a1293054fb3f9e65a15de6d63/aiohttp-3.14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1394dce36e0f0d260ac0b555a654de19cb989f3c1b8bdd24f505314dfea18a00", size = 1740325, upload-time = "2026-06-01T19:37:30.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/7e/e1899b1ca3ec62f1eab2a5cbde14039b97493f7f53eb88d9b668562ffa8d/aiohttp-3.14.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d1467d1e7b48a73ca7237e0ee4335f3d02b923dbc27b82fd254bc301c97d4026", size = 1748691, upload-time = "2026-06-01T19:37:32.211Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/54/4e6b61c1fe7d3433f82bcc6bd7e4d7c683a742a10c9b12a025fd3695c047/aiohttp-3.14.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6a5f3532125233c261cf61f32df4059cfcf482eb793c7d3db8452e3142028b86", size = 1814477, upload-time = "2026-06-01T19:37:34.173Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/38/86fd51be2e08d8e45c83d879d255f10391903cd9fe2a16512f7591a15873/aiohttp-3.14.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3ea81eb518a2ecb319d8ec6d1424a37c773f6634bd87d6985eb606b2faac419f", size = 1623393, upload-time = "2026-06-01T19:37:36.281Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/49/466e947a42a88ee23c486d036e7e5d1b097f1bafd8084ad9c9a0a92f0f43/aiohttp-3.14.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:32e735c3182de7b64f6941a4ede48b38c7f47d9437bd615dd30b5bda8fa1bc93", size = 1824097, upload-time = "2026-06-01T19:37:38.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/89/35f3410bc284682338a1be6b6ea0c5abfa05f063942cfaa9256608440434/aiohttp-3.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c21ca9a1c63d4509158f478aeb9d02914dcc52adc68d1bc9dee2452284ee5996", size = 1764790, upload-time = "2026-06-01T19:37:40.755Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/80/2d4291bd5724d3d17e5951aff5a3e02281483fb47295f0788276ee66cd73/aiohttp-3.14.0-cp311-cp311-win32.whl", hash = "sha256:19ca5fc84130675ba11c6ca5c7da5cb65f7bf8a32cdd2b616bf49cd334688aae", size = 454176, upload-time = "2026-06-01T19:37:42.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/ed/41d0ad4f6ececffc32bdf1f7b494e5498f7ca5c849ea2e3cc9bbd1668251/aiohttp-3.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:d488e6e9d3bb8ba5ae7066d5be885ae9670eba021b8c6ccb9a3a568e6b19d6e5", size = 479334, upload-time = "2026-06-01T19:37:44.776Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/86/c0b5e305c770053f8c3d069bb52b8196917ba91949d1962d52eb307fb0d2/aiohttp-3.14.0-cp311-cp311-win_arm64.whl", hash = "sha256:8b93618102caf12801638a01a2b478a55410ddd71bd41cfaf6f707953a49ac43", size = 450262, upload-time = "2026-06-01T19:37:46.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/97/2b6889bfb6b6847520d50d95eb8c4307a45e28aaca39faf4a9454b3d1b2f/aiohttp-3.14.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b29518c9c2ec7e373e68259206a137c7f4f5439c58baaec4b5ab3ab799850a4e", size = 750194, upload-time = "2026-06-01T19:37:48.164Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/e2/62634b7fff918ed98c3c6b2f0e70d520f7f28846cb412d451b04354c6459/aiohttp-3.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dbec68ce61b64cb73cab4d33df9433427b1713c8bcccb181dce695c1b6f8e87c", size = 506966, upload-time = "2026-06-01T19:37:50.014Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/fb/5ce075150828c797a5106f1c2fb26034e709d4289b9d2bf8b07f1e59fac6/aiohttp-3.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cdf534aa455593e589302990c5097aa5c92c06c4262a20da22934f9186a5fff", size = 507527, upload-time = "2026-06-01T19:37:51.96Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/d5/405a0ae4e6b081754a3609c1c97c63a950e000a2def16046f1e736933a0e/aiohttp-3.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb6c657104393b5fbff01a5f59b2023db74058a8077d94475d6c25d03882a108", size = 1762420, upload-time = "2026-06-01T19:37:53.839Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/1d/e05a7c896b15a6bc6fb8fc5319eb437861c2c49c34559ef928add6590315/aiohttp-3.14.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:46fbbec4e4fab7428d4396a3823f9320e4560aa3113b89eeebce712c27c9ed5a", size = 1733672, upload-time = "2026-06-01T19:37:55.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/22/a72f7c459e195fa41bf4f7abd1f925b91fe91f8097e51c654229ba144a33/aiohttp-3.14.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c2c7e05dd5335b298085abf45ddf98673934c3ee1c083d0b9ea13d4186ad500", size = 1805064, upload-time = "2026-06-01T19:37:57.931Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/50/e85bdaba0be59ca4838005ebfef4048fcdd5f35a02b07057a9a123394440/aiohttp-3.14.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3c7139100fbaae76515b73051d8f0aa3a3ff02e415eec8a8eee8e2223d9ba955", size = 1902125, upload-time = "2026-06-01T19:38:00.225Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/d8/51de5c6b971c27bb1ef620293b8d1ca611ec78736b34b3f6ccf68e4c8785/aiohttp-3.14.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78d6f9286a629ce52728430afe18f8ed2b6c39a1fddb3802d7244b9983910ad2", size = 1783112, upload-time = "2026-06-01T19:38:02.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/ae/b4402bfde77e43dfb1b6ccff83c7b7ab63ed06b50c4754f0c5423fb374fe/aiohttp-3.14.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3c3e12cdaeb92d7dcf13db00e9f6b1956b910e47256e696df1cfa946d02159", size = 1586356, upload-time = "2026-06-01T19:38:04.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/05/750a3265ca4dc54a460bd0cb1121a8f2ce9171fce4a135fb47ea7fd594d2/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4d6a998191f5ebe3b8c28463ff72bc030250008b3193c402464efadd08b5ca02", size = 1723119, upload-time = "2026-06-01T19:38:06.713Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/01/8c0812c50b3b1b1c37b323bf170d6be8847a8f234060485b7d1e71953f60/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0fc2b75ae8d169d853be2862d960be8550da6c5c65711d5476407eb3fdb006bd", size = 1757216, upload-time = "2026-06-01T19:38:08.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/2a/50fb98028a26887cbe48dcc1df92a90825615bc73b5584301304090cded8/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:16eee56bcc72d04600bc56c1759982c2385ec0b41d3fd3521f836bf64a0957ef", size = 1770500, upload-time = "2026-06-01T19:38:11.111Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/32/0ffd598a2fa2b9a423daf242e700cfdabda35d6e602394ad9ae58972c1c7/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5a2e7ca615c3ddc15b82687e05a624e5f5cba3f1d6c20cb81172d70ea498451e", size = 1576224, upload-time = "2026-06-01T19:38:13.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/f9/b9fc381dd9b66afb33f2634c40e229d106467be0afcabe79648631ab6712/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f0b7b8bbbec3ce9467ee0ebe334622fd90624f593edd3136c567811453fc4fae", size = 1794252, upload-time = "2026-06-01T19:38:15.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/fb/05d9214c975f23225a8cd5c439325e338c7c377b315480ef3871db51f54e/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ba10966d4f03dd96a14365be4b8e37c327c76f11c3ca867116966cdd9f98066", size = 1760193, upload-time = "2026-06-01T19:38:17.624Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/4b/02992fc4fb9e1b6673ee3f888a8e587a6447afda1f6f4aca776c148c2876/aiohttp-3.14.0-cp312-cp312-win32.whl", hash = "sha256:101df7779c80c0636014a6b2c6642acd3efb5b355d48347c9d7dfb720aee9430", size = 448650, upload-time = "2026-06-01T19:38:19.545Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/e9/246532214c3abda518477cbaaf16d420295ad8effa5233844cbb38f299ab/aiohttp-3.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:b0a5747586d4467efd1f932710b269131c9717a872dce082cd92a00c1c13123a", size = 476145, upload-time = "2026-06-01T19:38:21.505Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/c3/63f8c20090048915711598b0adf475b149216d736157961de06480a45b15/aiohttp-3.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:5f1c5be60add78fabb4aacd13c5a348ae79d2fcbfc7fa78da8f1eb192273b370", size = 444250, upload-time = "2026-06-01T19:38:24.027Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -860,7 +853,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/9a/7d/b22cb9a0d4f396ee0
|
||||
|
||||
[[package]]
|
||||
name = "alibabacloud-tea-openapi"
|
||||
version = "0.4.5"
|
||||
version = "0.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "alibabacloud-credentials" },
|
||||
@@ -869,9 +862,9 @@ dependencies = [
|
||||
{ name = "cryptography" },
|
||||
{ name = "darabonba-core" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3b/73/fb0c4d44759791ecdf269fc715c1e810fa1aba3981bfaaf8a01f61899296/alibabacloud_tea_openapi-0.4.5.tar.gz", hash = "sha256:75fa1f4360a46e41f5bf5f8d4917e52efb6f64885839bc1328c35590670c97b9", size = 26616, upload-time = "2026-07-14T13:15:39.364Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/30/93/138bcdc8fc596add73e37cf2073798f285284d1240bda9ee02f9384fc6be/alibabacloud_tea_openapi-0.4.4.tar.gz", hash = "sha256:1b0917bc03cd49417da64945e92731716d53e2eb8707b235f54e45b7473221ce", size = 21960, upload-time = "2026-03-26T10:16:16.792Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/ec/6b368a10e9c2e8b1b394c69b96ac213ae66e8c4895e0baa1ffaf7178fd32/alibabacloud_tea_openapi-0.4.5-py3-none-any.whl", hash = "sha256:338979095c7beda80a5b413c31262892cafdc12069dde4ce4fc2e4f7ce0fc609", size = 33333, upload-time = "2026-07-14T13:15:38.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/5a/6bfc4506438c1809c486f66217ad11eab78157192b3d5707b4e2f4212f6c/alibabacloud_tea_openapi-0.4.4-py3-none-any.whl", hash = "sha256:cea6bc1fe35b0319a8752cb99eb0ecb0dab7ca1a71b99c12970ba0867410995f", size = 26236, upload-time = "2026-03-26T10:16:15.861Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2095,37 +2088,6 @@ toml = [
|
||||
{ name = "tomli", marker = "python_full_version <= '3.11'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32c"
|
||||
version = "2.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e3/66/7e97aa77af7cf6afbff26e3651b564fe41932599bc2d3dce0b2f73d4829a/crc32c-2.8.tar.gz", hash = "sha256:578728964e59c47c356aeeedee6220e021e124b9d3e8631d95d9a5e5f06e261c", size = 48179, upload-time = "2025-10-17T06:20:13.61Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/0b/5e03b22d913698e9cc563f39b9f6bbd508606bf6b8e9122cd6bf196b87ea/crc32c-2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e560a97fbb96c9897cb1d9b5076ef12fc12e2e25622530a1afd0de4240f17e1f", size = 66329, upload-time = "2025-10-17T06:19:01.771Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/38/2fe0051ffe8c6a650c8b1ac0da31b8802d1dbe5fa40a84e4b6b6f5583db5/crc32c-2.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6762d276d90331a490ef7e71ffee53b9c0eb053bd75a272d786f3b08d3fe3671", size = 62988, upload-time = "2025-10-17T06:19:02.953Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/30/5837a71c014be83aba1469c58820d287fc836512a0cad6b8fdd43868accd/crc32c-2.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:60670569f5ede91e39f48fb0cb4060e05b8d8704dd9e17ede930bf441b2f73ef", size = 61522, upload-time = "2025-10-17T06:19:03.796Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/29/63972fc1452778e2092ae998c50cbfc2fc93e3fa9798a0278650cd6169c5/crc32c-2.8-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:711743da6ccc70b3c6718c328947b0b6f34a1fe6a6c27cc6c1d69cc226bf70e9", size = 80200, upload-time = "2025-10-17T06:19:04.617Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/3a/60eb49d7bdada4122b3ffd45b0df54bdc1b8dd092cda4b069a287bdfcff4/crc32c-2.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5eb4094a2054774f13b26f21bf56792bb44fa1fcee6c6ad099387a43ffbfb4fa", size = 81757, upload-time = "2025-10-17T06:19:05.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/63/6efc1b64429ef7d23bd58b75b7ac24d15df327e3ebbe9c247a0f7b1c2ed1/crc32c-2.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fff15bf2bd3e95780516baae935ed12be88deaa5ebe6143c53eb0d26a7bdc7b7", size = 80830, upload-time = "2025-10-17T06:19:06.621Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/eb/0ae9f436f8004f1c88f7429e659a7218a3879bd11a6b18ed1257aad7e98b/crc32c-2.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c0e11e3826668121fa53e0745635baf5e4f0ded437e8ff63ea56f38fc4f970a", size = 80095, upload-time = "2025-10-17T06:19:07.381Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/81/4afc9d468977a4cd94a2eb62908553345009a7c0d30e74463a15d4b48ec3/crc32c-2.8-cp311-cp311-win32.whl", hash = "sha256:38f915336715d1f1353ab07d7d786f8a789b119e273aea106ba55355dfc9101d", size = 64886, upload-time = "2025-10-17T06:19:08.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/e8/94e839c9f7e767bf8479046a207afd440a08f5c59b52586e1af5e64fa4a0/crc32c-2.8-cp311-cp311-win_amd64.whl", hash = "sha256:60e0a765b1caab8d31b2ea80840639253906a9351d4b861551c8c8625ea20f86", size = 66639, upload-time = "2025-10-17T06:19:09.338Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/36/fd18ef23c42926b79c7003e16cb0f79043b5b179c633521343d3b499e996/crc32c-2.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:572ffb1b78cce3d88e8d4143e154d31044a44be42cb3f6fbbf77f1e7a941c5ab", size = 66379, upload-time = "2025-10-17T06:19:10.115Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/b8/c584958e53f7798dd358f5bdb1bbfc97483134f053ee399d3eeb26cca075/crc32c-2.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cf827b3758ee0c4aacd21ceca0e2da83681f10295c38a10bfeb105f7d98f7a68", size = 63042, upload-time = "2025-10-17T06:19:10.946Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/e6/6f2af0ec64a668a46c861e5bc778ea3ee42171fedfc5440f791f470fd783/crc32c-2.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:106fbd79013e06fa92bc3b51031694fcc1249811ed4364ef1554ee3dd2c7f5a2", size = 61528, upload-time = "2025-10-17T06:19:11.768Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/8b/4a04bd80a024f1a23978f19ae99407783e06549e361ab56e9c08bba3c1d3/crc32c-2.8-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6dde035f91ffbfe23163e68605ee5a4bb8ceebd71ed54bb1fb1d0526cdd125a2", size = 80028, upload-time = "2025-10-17T06:19:12.554Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/8f/01c7afdc76ac2007d0e6a98e7300b4470b170480f8188475b597d1f4b4c6/crc32c-2.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e41ebe7c2f0fdcd9f3a3fd206989a36b460b4d3f24816d53e5be6c7dba72c5e1", size = 81531, upload-time = "2025-10-17T06:19:13.406Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/2b/8f78c5a8cc66486be5f51b6f038fc347c3ba748d3ea68be17a014283c331/crc32c-2.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ecf66cf90266d9c15cea597d5cc86c01917cd1a238dc3c51420c7886fa750d7e", size = 80608, upload-time = "2025-10-17T06:19:14.223Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/86/fad1a94cdeeeb6b6e2323c87f970186e74bfd6fbfbc247bf5c88ad0873d5/crc32c-2.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:59eee5f3a69ad0793d5fa9cdc9b9d743b0cd50edf7fccc0a3988a821fef0208c", size = 79886, upload-time = "2025-10-17T06:19:15.345Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/db/1a7cb6757a1e32376fa2dfce00c815ea4ee614a94f9bff8228e37420c183/crc32c-2.8-cp312-cp312-win32.whl", hash = "sha256:a73d03ce3604aa5d7a2698e9057a0eef69f529c46497b27ee1c38158e90ceb76", size = 64896, upload-time = "2025-10-17T06:19:16.457Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/8e/2024de34399b2e401a37dcb54b224b56c747b0dc46de4966886827b4d370/crc32c-2.8-cp312-cp312-win_amd64.whl", hash = "sha256:56b3b7d015247962cf58186e06d18c3d75a1a63d709d3233509e1c50a2d36aa2", size = 66645, upload-time = "2025-10-17T06:19:17.235Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/1d/dd926c68eb8aac8b142a1a10b8eb62d95212c1cf81775644373fe7cceac2/crc32c-2.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5833f4071da7ea182c514ba17d1eee8aec3c5be927d798222fbfbbd0f5eea02c", size = 62345, upload-time = "2025-10-17T06:20:09.39Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/be/803404e5abea2ef2c15042edca04bbb7f625044cca879e47f186b43887c2/crc32c-2.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1dc4da036126ac07b39dd9d03e93e585ec615a2ad28ff12757aef7de175295a8", size = 61229, upload-time = "2025-10-17T06:20:10.236Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/3a/00cc578cd27ed0b22c9be25cef2c24539d92df9fa80ebd67a3fc5419724c/crc32c-2.8-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:15905fa78344654e241371c47e6ed2411f9eeb2b8095311c68c88eccf541e8b4", size = 64108, upload-time = "2025-10-17T06:20:11.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/bc/0587ef99a1c7629f95dd0c9d4f3d894de383a0df85831eb16c48a6afdae4/crc32c-2.8-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c596f918688821f796434e89b431b1698396c38bf0b56de873621528fe3ecb1e", size = 64815, upload-time = "2025-10-17T06:20:11.919Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/42/94f2b8b92eae9064fcfb8deef2b971514065bd606231f8857ff8ae02bebd/crc32c-2.8-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8d23c4fe01b3844cb6e091044bc1cebdef7d16472e058ce12d9fadf10d2614af", size = 66659, upload-time = "2025-10-17T06:20:12.766Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cron-descriptor"
|
||||
version = "1.4.5"
|
||||
@@ -2150,45 +2112,47 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "50.0.0"
|
||||
version = "46.0.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/3e/e54cde8c01631a5a8226ccd617eab9e57fd5cfdad90f1a9e6bb570794631/cryptography-50.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5e34edd123674534acd70147f0ca331eaa2c74e6325fb2028c886aa26ba0b68c", size = 3963170, upload-time = "2026-07-31T14:24:51.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/b6/0b9e125e90f3d2dcf599a218a899cda7326a3158cfa258723f0b398b08f6/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8eb5e1172eb569ea8a872796576e6a67c276351728b6455d5beb01242b027c6a", size = 4692441, upload-time = "2026-07-31T14:24:53.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/c9/a5151588710785a96d7bc4de27d4cd62f263bbbcb203cfe29df537eb6505/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:910d11e1a385c654bf738bf3e6b8e6ed5de0f5610fcae2be9e5b398d8081d20e", size = 4699810, upload-time = "2026-07-31T14:24:55.746Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/1a/15b92b25eb6ce3089cd49377ae990a0f3ad485a510f968aed1f19dbdcdf2/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:62598a8a57f815db4c6259a4e97d857dab56697e7de8e8ab02352ab74da1995d", size = 4691924, upload-time = "2026-07-31T14:24:58.082Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/15/219075012ab13e8905f3cd572204f4acb4b111df787104346b9bc0cea789/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:07479a1cb08219ab719147e742e76090c9c773321959bb94946fffdd397a6437", size = 4699593, upload-time = "2026-07-31T14:24:59.951Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/b5/c2c5fce26f0ee40d21bafe7f191d29a34b35a65ac4fe8a1191d1983612e9/cryptography-50.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c99c003e088647b8a5b7c145d6f78c335f6348332b62e142d411c4b63d1460b9", size = 3813796, upload-time = "2026-07-31T14:25:02.298Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/0c/dca8abb64e7ca4f6b2978769f6fea5ad06686a190cec381f0a796fdcaaba/cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f", size = 3476879, upload-time = "2026-04-08T01:57:38.664Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/ea/075aac6a84b7c271578d81a2f9968acb6e273002408729f2ddff517fed4a/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15", size = 4219700, upload-time = "2026-04-08T01:57:40.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/7b/1c55db7242b5e5612b29fc7a630e91ee7a6e3c8e7bf5406d22e206875fbd/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455", size = 4385982, upload-time = "2026-04-08T01:57:42.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/da/9870eec4b69c63ef5925bf7d8342b7e13bc2ee3d47791461c4e49ca212f4/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65", size = 4219115, upload-time = "2026-04-08T01:57:44.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/72/05aa5832b82dd341969e9a734d1812a6aadb088d9eb6f0430fc337cc5a8f/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968", size = 4385479, upload-time = "2026-04-08T01:57:46.86Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/2a/1b016902351a523aa2bd446b50a5bc1175d7a7d1cf90fe2ef904f9b84ebc/cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4", size = 3412829, upload-time = "2026-04-08T01:57:48.874Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2202,17 +2166,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "darabonba-core"
|
||||
version = "1.0.8"
|
||||
version = "1.0.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "alibabacloud-tea" },
|
||||
{ name = "requests" },
|
||||
{ name = "websocket-client" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/83/9321ccdb7a800c2cb97d8fa34bead5f20141f27f804594fd1fd815c4cd07/darabonba_core-1.0.8.tar.gz", hash = "sha256:f1661960b368e342d3d36434be82d264b70a01c49e843921d8a4dacd217376ae", size = 27604, upload-time = "2026-07-13T02:07:34.093Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/88/38800ca22f39a31fdb75c7b2867c61d3af5e2792cee0b72942a639c88a79/darabonba_core-1.0.8-py3-none-any.whl", hash = "sha256:ac093fdd40f88f2f9dfbbbfd7bc143495a3cb031f35b397c98d24edfa6b69483", size = 30957, upload-time = "2026-07-13T02:07:33.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/d3/a7daaee544c904548e665829b51a9fa2572acb82c73ad787a8ff90273002/darabonba_core-1.0.5-py3-none-any.whl", hash = "sha256:671ab8dbc4edc2a8f88013da71646839bb8914f1259efc069353243ef52ea27c", size = 24580, upload-time = "2025-12-12T07:53:59.494Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3365,14 +3327,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "httplib2"
|
||||
version = "0.32.0"
|
||||
version = "0.31.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyparsing" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/84/f5/ccf58de92d61e3ad921119668f54ed36ca1d0cf5dcc5c1657dfb164fd78b/httplib2-0.32.0.tar.gz", hash = "sha256:48a0ef30a42db65d8f3399045e1d09ab0ba66e3b9efc360d07f80ea55d286025", size = 254283, upload-time = "2026-06-26T10:13:56.265Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c1/1f/e86365613582c027dda5ddb64e1010e57a3d53e99ab8a72093fa13d565ec/httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24", size = 250800, upload-time = "2026-01-23T11:04:44.165Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/33/a0/550eec327e5f5c7b732531c489f5307efec41f047b0d703bd4ca1e5ad2db/httplib2-0.32.0-py3-none-any.whl", hash = "sha256:dc6705cacdf3fb0a2aba7629fa33c90fd93e30035db0c157325826be177e4816", size = 93148, upload-time = "2026-06-26T10:13:54.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/90/fd509079dfcab01102c0fdd87f3a9506894bc70afcf9e9785ef6b2b3aff6/httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349", size = 91099, upload-time = "2026-01-23T11:04:42.78Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3395,134 +3357,6 @@ http2 = [
|
||||
{ name = "h2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "huaweicloudsdkcore"
|
||||
version = "3.1.204"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "defusedxml" },
|
||||
{ name = "pyasn1" },
|
||||
{ name = "pymongo" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "requests-toolbelt" },
|
||||
{ name = "simplejson" },
|
||||
{ name = "six" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/f5/65e90764ea3bbfef50fb68cd5e12340acf1f51e9276b11745fbf5feb7e0e/huaweicloudsdkcore-3.1.204-py3-none-any.whl", hash = "sha256:9ae17744795ebdc8ce9291373a3a27bf72e90aa98677cfce0ea9394376875a95", size = 69578, upload-time = "2026-07-09T09:01:59.715Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "huaweicloudsdkcts"
|
||||
version = "3.1.204"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "huaweicloudsdkcore" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/58/32/d06328e35375d4aa606719a27856cdf57b1f7fb0c49d4dfd22a9609dba19/huaweicloudsdkcts-3.1.204-py3-none-any.whl", hash = "sha256:9def561aa784a6ee13b46bfc96888cd1df5bfc42f8a89e60b42c91c608bf6d60", size = 121768, upload-time = "2026-07-09T09:02:08.16Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "huaweicloudsdkecs"
|
||||
version = "3.1.204"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "huaweicloudsdkcore" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/66/f8e4a3b9ca70d3ea79c4d200f928ed9ffdf4910ac01be4864967408c8f18/huaweicloudsdkecs-3.1.204-py3-none-any.whl", hash = "sha256:dc5715d782c0260b901c793d009d5e632257acb04257b6f2c6631e415c589343", size = 765699, upload-time = "2026-07-09T09:02:39.272Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "huaweicloudsdkelb"
|
||||
version = "3.1.204"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "huaweicloudsdkcore" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/34/4b/9bdc7e2066419d967e9b812cfacfb9c4996a8e977dc39853309a3c1ac9e2/huaweicloudsdkelb-3.1.204-py3-none-any.whl", hash = "sha256:620247c2b2a7f20e7da8b18fe9c64e29972055f015bc35270fb5b43243dc4830", size = 1292397, upload-time = "2026-07-09T09:02:45.656Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "huaweicloudsdkevs"
|
||||
version = "3.1.204"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "huaweicloudsdkcore" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/cf/531dc55fd9d0f3bbd3eef24c7e4d78c6a1ba8eb80fa506e3574d72bcc98a/huaweicloudsdkevs-3.1.204-py3-none-any.whl", hash = "sha256:9118ac4c576e54aa7eaa926949e2b6824c5f038a2274b51d9a304d37fc0d7e2f", size = 251404, upload-time = "2026-07-09T09:02:50.05Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "huaweicloudsdkiam"
|
||||
version = "3.1.204"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "huaweicloudsdkcore" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/9a/7da0fbe9b83bc7a7f6d586366b81e829ed355a57419d2184b7dd51f8c2a3/huaweicloudsdkiam-3.1.204-py3-none-any.whl", hash = "sha256:0021e204f81ceef2640017e517adb72ba56c9ced03f071a0265b10bc9759badf", size = 1251350, upload-time = "2026-07-09T09:03:05.467Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "huaweicloudsdkkms"
|
||||
version = "3.1.204"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "huaweicloudsdkcore" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/28/3a/7392617d585cb2005f7d9ade0b0e0e493a7a88daf56e8dc39e4e219cc5d0/huaweicloudsdkkms-3.1.204-py3-none-any.whl", hash = "sha256:378986f33113ce99f445ef318d1c7dda89e361d16c008e5ef9793981d8385376", size = 275690, upload-time = "2026-07-09T09:03:29.833Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "huaweicloudsdkobs"
|
||||
version = "3.1.204"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "huaweicloudsdkcore" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/af/49/28a09e1e33d1c039be22ee4171efaa739351653c7aa88d3a2f7a78d90217/huaweicloudsdkobs-3.1.204-py3-none-any.whl", hash = "sha256:8c5830fa30293185964d98e524887fc510c8e17ca2fadb4563dad10910f37b13", size = 235360, upload-time = "2026-07-09T09:03:52.171Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "huaweicloudsdkrds"
|
||||
version = "3.1.204"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "huaweicloudsdkcore" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/7d/721f162c46e3de604a73674223bf6c6bc6cf7ade25b3751a71288f4dd122/huaweicloudsdkrds-3.1.204-py3-none-any.whl", hash = "sha256:a790b5b3c457a608e5679c101f463b4d037dd9a8a66f6e46144a9e5a4b37780f", size = 1626906, upload-time = "2026-07-09T09:04:06.936Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "huaweicloudsdkvpc"
|
||||
version = "3.1.204"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "huaweicloudsdkcore" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/b5/4baa27c3a275ea92806068e35e06f249a30add8dd57c777bb45841f63406/huaweicloudsdkvpc-3.1.204-py3-none-any.whl", hash = "sha256:c57d6b6d2f70deca91e86f7956b33fc9ac4991b431f0d608c3632231119f8970", size = 1124332, upload-time = "2026-07-09T09:04:39.797Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "huaweicloudsdkwaf"
|
||||
version = "3.1.204"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "huaweicloudsdkcore" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/21/01590dce200be487756451f5e9efb99da7810688062d177e4b58d6062465/huaweicloudsdkwaf-3.1.204-py3-none-any.whl", hash = "sha256:b2355276e0029808f45e2d1bd3eb14b37d2da9417e61e642ffe0e2b748ca8283", size = 1337762, upload-time = "2026-07-09T09:04:43.688Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humanfriendly"
|
||||
version = "10.0"
|
||||
@@ -4086,21 +3920,21 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "microsoft-kiota-abstractions"
|
||||
version = "1.9.10"
|
||||
version = "1.9.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-sdk" },
|
||||
{ name = "std-uritemplate" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/45/e1/39de28380fc0eddf12f66099469fb7561bc38f577ea06e3a074751ebbcd9/microsoft_kiota_abstractions-1.9.10.tar.gz", hash = "sha256:8eb62d64c35ad0eeb4e8bcdbb143c0b308dc4a494e757f8e44cb959d34f44ecf", size = 24473, upload-time = "2026-03-12T17:27:15.398Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8f/94/37315b82a1bcc08145e5bc2af7396a4be8160ac138ec269611c3b9589b7a/microsoft_kiota_abstractions-1.9.9.tar.gz", hash = "sha256:5df9a8e0517a4568726c2cac6d9789284cc6ffa66043b68eba42ae55749fb861", size = 24468, upload-time = "2026-03-02T21:03:50.133Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/59/bf0cb26c80fbd3fa882df8474ad87e9dbd742656c376388c427c4e314171/microsoft_kiota_abstractions-1.9.10-py3-none-any.whl", hash = "sha256:cd169067ebe48e6feea1258630807034239e0c61c2abe5fd66896a58177e8f05", size = 44462, upload-time = "2026-03-12T17:27:16.532Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/6a/7d5a1a8131f0eccc6b45839c091aa00ba29661854e7defaa7936cf342fa7/microsoft_kiota_abstractions-1.9.9-py3-none-any.whl", hash = "sha256:8d0a14eda42f3f0ccac2e9512227a338f69998dc9b782fd21cb8ca7c48302caa", size = 44453, upload-time = "2026-03-02T21:03:51.11Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "microsoft-kiota-authentication-azure"
|
||||
version = "1.9.10"
|
||||
version = "1.9.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
@@ -4109,14 +3943,14 @@ dependencies = [
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-sdk" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d5/53/7760f979c141ec590f0c1cfcb92b3e410eb2909cc19feb42f3fce78db171/microsoft_kiota_authentication_azure-1.9.10.tar.gz", hash = "sha256:b9f10a9fa86e36114abfee448d2dab91a502d6a55d349a306e2e41a1218fe1ad", size = 4999, upload-time = "2026-03-12T17:27:26.323Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ca/ce/5ae8b37ee4a50f0ed5e092c2d0105d60b592e6102a190959f76658a0994c/microsoft_kiota_authentication_azure-1.9.9.tar.gz", hash = "sha256:aca5e7dc8a0a28224f9025a479349ac2f9aaf166bfd6bc707f232658b45eec28", size = 5000, upload-time = "2026-03-02T21:04:02.355Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/4a/e7852f9358d897ada1eec4e825c815761befe36df4defa79f1ae6c7b588c/microsoft_kiota_authentication_azure-1.9.10-py3-none-any.whl", hash = "sha256:b5d98b0d17173c61c0c7ab4274ea4ca69253b3c13424137758034506694964e9", size = 6961, upload-time = "2026-03-12T17:27:27.238Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/de/dc504324b776d00a420886cc6f39e04be2cf48cab0e9b18f8450a5efcc29/microsoft_kiota_authentication_azure-1.9.9-py3-none-any.whl", hash = "sha256:73dc21a1a2861ea78a135327291db3322e2255542a18b311dd03fd908342e902", size = 6951, upload-time = "2026-03-02T21:04:03.18Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "microsoft-kiota-http"
|
||||
version = "1.9.10"
|
||||
version = "1.9.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx", extra = ["http2"] },
|
||||
@@ -4124,57 +3958,57 @@ dependencies = [
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-sdk" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a7/e5/20972b620bd8cca086c284e97b285d437c108a23fee122ad7b92bd246c1a/microsoft_kiota_http-1.9.10.tar.gz", hash = "sha256:af1838d091f76426c974897357093ed977ce66f1d808cb161c190de873bb5833", size = 21493, upload-time = "2026-03-12T17:27:35.393Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5d/3f/fc18eb0d1d845daf6355fd54fd990af7f7e10043ef6a6da39b9e5981cbaf/microsoft_kiota_http-1.9.9.tar.gz", hash = "sha256:ae672b145df71b644f8da0951767a12a4ce47a40576d86eba19b7c22d9e160f9", size = 21493, upload-time = "2026-03-02T21:04:11.662Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/f4/78ce18330a626138b2ff6bb62574adac01e8b9ee87c1349ddfeb9cab0556/microsoft_kiota_http-1.9.10-py3-none-any.whl", hash = "sha256:6127032c8d94f8607e4d36d0822b88bc8689ab368b4c00d6c7beb7d2d0f2ab10", size = 31960, upload-time = "2026-03-12T17:27:36.1Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/6a/cc1b1055b4b6d4dfc1be7a71917c2f0ef19c070c6a18b16d3c1032d20925/microsoft_kiota_http-1.9.9-py3-none-any.whl", hash = "sha256:a5b1b217ac9afeb4054f12515417e3b1d2be12a9385a70a41d18d64379ea2e7e", size = 31945, upload-time = "2026-03-02T21:04:12.328Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "microsoft-kiota-serialization-form"
|
||||
version = "1.9.10"
|
||||
version = "1.9.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "microsoft-kiota-abstractions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/56/90/7e1a090a2099acae1a1baa9a0762214b73b63d9268369b510994f75f54e4/microsoft_kiota_serialization_form-1.9.10.tar.gz", hash = "sha256:4c6655d8cd479d1ada63fdfe6a272e50d87d7c8369dbc8e13833ba4787fc798b", size = 9012, upload-time = "2026-03-12T17:27:44.214Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/b4/18e9fce60a30c8b6ea0a6278fb81cf352127340d48df2d7c52ff1b579488/microsoft_kiota_serialization_form-1.9.9.tar.gz", hash = "sha256:3cdc8b172baec5b5282af72f2ce02715edcd23252ce0b5af96075256edd75114", size = 9015, upload-time = "2026-03-02T21:04:20.39Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/4c/5092fc896b34c21e8b9c03c63006b313a81e2377176a69c97aa6a9c8f5bb/microsoft_kiota_serialization_form-1.9.10-py3-none-any.whl", hash = "sha256:765d3f6408668f58bfdf892c32b45967c579d9131f3ba5a6b6868cb7ab956bfe", size = 10728, upload-time = "2026-03-12T17:27:45.103Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/24/eb8436b882f1473bd0a868848d214df3df2d9b3db8e5422d111032f1114f/microsoft_kiota_serialization_form-1.9.9-py3-none-any.whl", hash = "sha256:1c426d4f0d463fc9215c41d7fa0f3dc5fe8d3c80573d555cf63ea67000148d84", size = 10718, upload-time = "2026-03-02T21:04:21.25Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "microsoft-kiota-serialization-json"
|
||||
version = "1.9.10"
|
||||
version = "1.9.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "microsoft-kiota-abstractions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/67/0e/55afd533a764ba77da988b7ca4242c84867a3a25f2ff0bf4c2b24b5e8fca/microsoft_kiota_serialization_json-1.9.10.tar.gz", hash = "sha256:6063028f30dd67afa2db20a72d9bde5e5d26d468f8bdedadd1445cf7c7630e17", size = 9746, upload-time = "2026-03-12T17:27:53.015Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b8/2f/d36eba916c00136da122d1701acb862c5b1f2e22b6dc6fa4e0f4abda2786/microsoft_kiota_serialization_json-1.9.9.tar.gz", hash = "sha256:9b27479427f49bbac15ead8e8ff0176e47fcdf81153611acc408f5f399342079", size = 9545, upload-time = "2026-03-02T21:04:29.177Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/56/d14c0185c8092abde1a60ad2bdd4480bb2ddb551ce71c6de1e6133a4d8d1/microsoft_kiota_serialization_json-1.9.10-py3-none-any.whl", hash = "sha256:0545ae910160b19caaa8c30c90c7416e1966294fbd6cc5af01f0e116a18f223a", size = 11452, upload-time = "2026-03-12T17:27:53.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/7b/b3f606ef2dcbdebe12ae27004ed6e7542370cb2494265f11a8877a1de2d1/microsoft_kiota_serialization_json-1.9.9-py3-none-any.whl", hash = "sha256:bb80b93e81bab41dc142e9b254f79bf0b7b9fe49a796ca0c8e8691925bd3967f", size = 11210, upload-time = "2026-03-02T21:04:29.844Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "microsoft-kiota-serialization-multipart"
|
||||
version = "1.9.10"
|
||||
version = "1.9.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "microsoft-kiota-abstractions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/24/34/eadc15c2a3131e2a76126f3112c32b73502cb5a335e2e40cac2877e5d843/microsoft_kiota_serialization_multipart-1.9.10.tar.gz", hash = "sha256:8f2da4f93e79b09f9738b6889685e47acfafcca870db94ab1d4cd233d69e4268", size = 5167, upload-time = "2026-03-12T17:28:18.507Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/44/24087f0fac7c5682c13c7fb61468a0c5a5185b9f243de3a99309aa6fcaa7/microsoft_kiota_serialization_multipart-1.9.9.tar.gz", hash = "sha256:f8730be6da5f6c63a6bf4ea310a9723b9998a47a04745887dc156d08f119a829", size = 5162, upload-time = "2026-03-02T21:04:48.1Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/40/345cbcee6c52b4261fedf4ae2ff8573aec47ce4ae2015ea8b57c75ef978b/microsoft_kiota_serialization_multipart-1.9.10-py3-none-any.whl", hash = "sha256:7cadc26483b567c738f926b044521569e0b797446053c9e8eab02269d4a81062", size = 6708, upload-time = "2026-03-12T17:28:19.397Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/db/6b988fdf771c3d07dff4a116176d575832daf2a43823444d145d71da5b61/microsoft_kiota_serialization_multipart-1.9.9-py3-none-any.whl", hash = "sha256:572e9cbafa2eb946452cdadfb019a4e9245768c0d61c3089d3436d4f5106c550", size = 6696, upload-time = "2026-03-02T21:04:48.98Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "microsoft-kiota-serialization-text"
|
||||
version = "1.9.10"
|
||||
version = "1.9.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "microsoft-kiota-abstractions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/74/a6/28a4a8d5c01f08e363135fc9585cab3c02d1b1a69c3c16032e6abb35dfed/microsoft_kiota_serialization_text-1.9.10.tar.gz", hash = "sha256:cfc433c2a95ea3c3ec43c8b09002fbf65c998c5c0571205df161fe0e9d5d8de7", size = 7326, upload-time = "2026-03-12T17:28:01.621Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/3c/d244ad08e03003134871698aa54de8243bcc61c0faf3ab114293bb76d6ad/microsoft_kiota_serialization_text-1.9.9.tar.gz", hash = "sha256:18bc0764dda4078a4c953300253344e05d0cdb9c17136f1a2f695d438cedb402", size = 7325, upload-time = "2026-03-02T21:04:37.567Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/bf/dd36e4a6d1cff3f2d30f03e2479cd38210e32d4715bb6a9f0e2737f13604/microsoft_kiota_serialization_text-1.9.10-py3-none-any.whl", hash = "sha256:742890cfd4450d12f58d42da7cfa474fe1ee5d6442e016bf70ab76e5c876c0ea", size = 8896, upload-time = "2026-03-12T17:28:02.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/f8/43f8d00fed6e090810d3ce0c05e06c23eaa5dee6e87ab1fb89d96ca9559f/microsoft_kiota_serialization_text-1.9.9-py3-none-any.whl", hash = "sha256:84418119d4929a76fde7f31e957e240e003bf145757838b9aa3a0f36dec1b789", size = 8885, upload-time = "2026-03-02T21:04:38.76Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4426,22 +4260,20 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "oci"
|
||||
version = "2.183.0"
|
||||
version = "2.169.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "circuitbreaker" },
|
||||
{ name = "crc32c" },
|
||||
{ name = "cryptography" },
|
||||
{ name = "pyjwt", extra = ["crypto"] },
|
||||
{ name = "pyopenssl" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "pytz" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1e/2a/77bd6cbf1c69b2f368fe3d6462d84369b0cba15e37ce713cdc08d459b95a/oci-2.183.0.tar.gz", hash = "sha256:ff572ef5f2030a788796bb509d257e6a41c6510ef9b4b6a75a079efd06e533ce", size = 17759723, upload-time = "2026-07-28T06:02:29.76Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/11/f4/3c2eddccc75dd06a692dbb3290f20f4bc733d99dc60de21f22d65efdeae4/oci-2.169.0.tar.gz", hash = "sha256:f3c5fff00b01783b5325ea7b13bf140053ec1e9f41da20bfb9c8a349ee7662fa", size = 16885837, upload-time = "2026-03-31T06:14:58.981Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/de/8574b3e527996a099d196e87794a4652d91a0c3185fcc7fdbb5649b75a8a/oci-2.183.0-py3-none-any.whl", hash = "sha256:bd789c98a94d7c5ea08c20d11dcf68c9cd1ad479b134727d80a930b84387070b", size = 36133501, upload-time = "2026-07-28T06:02:18.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/bf/19643bd939ab595193779ee25c2c12aef8e9a54e0a68de5ed79f209702e3/oci-2.169.0-py3-none-any.whl", hash = "sha256:c71bb5143f307791082b3e33cc1545c2490a518cfed85ab1948ef5107c36d30b", size = 34460447, upload-time = "2026-03-31T06:14:51.373Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4635,33 +4467,39 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.3.0"
|
||||
version = "12.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4835,8 +4673,8 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "prowler"
|
||||
version = "5.38.0"
|
||||
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=master#b3d174d0c1eb202ed7cb9a9daf0500683f4443be" }
|
||||
version = "5.35.0"
|
||||
source = { git = "https://github.com/prowler-cloud/prowler.git?rev=master#f5ea116763aeffede9f399c8934fc280eaccd315" }
|
||||
dependencies = [
|
||||
{ name = "alibabacloud-actiontrail20200706" },
|
||||
{ name = "alibabacloud-credentials" },
|
||||
@@ -4891,17 +4729,6 @@ dependencies = [
|
||||
{ name = "google-api-python-client" },
|
||||
{ name = "google-auth-httplib2" },
|
||||
{ name = "h2" },
|
||||
{ name = "huaweicloudsdkcore" },
|
||||
{ name = "huaweicloudsdkcts" },
|
||||
{ name = "huaweicloudsdkecs" },
|
||||
{ name = "huaweicloudsdkelb" },
|
||||
{ name = "huaweicloudsdkevs" },
|
||||
{ name = "huaweicloudsdkiam" },
|
||||
{ name = "huaweicloudsdkkms" },
|
||||
{ name = "huaweicloudsdkobs" },
|
||||
{ name = "huaweicloudsdkrds" },
|
||||
{ name = "huaweicloudsdkvpc" },
|
||||
{ name = "huaweicloudsdkwaf" },
|
||||
{ name = "jsonschema" },
|
||||
{ name = "kingfisher-bin" },
|
||||
{ name = "kubernetes" },
|
||||
@@ -4935,7 +4762,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prowler-api"
|
||||
version = "1.41.0"
|
||||
version = "1.38.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "cartography" },
|
||||
@@ -5151,24 +4978,25 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "py-ocsf-models"
|
||||
version = "0.10.0"
|
||||
version = "0.8.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
{ name = "email-validator" },
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/70/d6/f0787cbe953e3cf6ef4430f3cc7d66cbbaabe4b20cb82cc27cc2d21e622a/py_ocsf_models-0.10.0.tar.gz", hash = "sha256:29abaa5a3d4ebba0e2a21757508a4848fa5e1d57da233af57e580f97f0223c59", size = 36498, upload-time = "2026-07-13T07:05:44.448Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/70/61e2f9ce3d7e83aa5339ed6ae17e473c15c7a36f161c6dbea0e939e3af0c/py_ocsf_models-0.8.1.tar.gz", hash = "sha256:c9045237857f951e073c9f9d1f57954c90d86875b469260725292d47f7a7d73c", size = 36540, upload-time = "2026-02-12T16:50:15.233Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/75/56/eca45ec87a02f930cc7eaa7cb36660f69fb00c3d77bb4a84bb92d6c94c25/py_ocsf_models-0.10.0-py3-none-any.whl", hash = "sha256:a9d1e245b1c9fba1d2cb8c042253ef1b83a2dbfec30ed69975bbce599b4510bb", size = 64334, upload-time = "2026-07-13T07:05:42.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/18/63790884bf33f820e2c60f8d5038b5d6de967a03343ddf237c054e1d6d08/py_ocsf_models-0.8.1-py3-none-any.whl", hash = "sha256:061eb446c4171534c09a8b37f5a9d2a2fe9f87c5db32edbd1182446bc5fd097e", size = 64354, upload-time = "2026-02-12T16:50:12.983Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.6.4"
|
||||
version = "0.6.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a4/9a/23310166d960def5897e91fe20e5b724601b02a22e84ba1f94232c0b7f67/pyasn1-0.6.4.tar.gz", hash = "sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81", size = 151262, upload-time = "2026-07-09T01:12:33.988Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/3b/6163796d69c3977d1e4287bea4a6979161cbbdd170ebb430511e8e1999ce/pyasn1-0.6.4-py3-none-any.whl", hash = "sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b", size = 84410, upload-time = "2026-07-09T01:12:32.92Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5359,37 +5187,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/ff/7f52c1461d8ceaefa989d2700a027f84427879bb7571145bbffdec5d5f4a/pylint-3.2.5-py3-none-any.whl", hash = "sha256:32cd6c042b5004b8e857d727708720c54a676d1e22917cf1a2df9b4d4868abd6", size = 519603, upload-time = "2024-06-28T13:10:23.526Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pymongo"
|
||||
version = "4.15.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "dnspython" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/22/f5/c0c6732fbd358b75a07e17d7e588fd23d481b9812ca96ceeff90bbf879fc/pymongo-4.15.1.tar.gz", hash = "sha256:b9f379a4333dc3779a6bf7adfd077d4387404ed1561472743486a9c58286f705", size = 2470613, upload-time = "2025-09-16T16:39:47.24Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/da/89066930a70b4299844f1155fc23baaa7e30e77c8a0cbf62a2ae06ee34a5/pymongo-4.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:363445cc0e899b9e55ac9904a868c8a16a6c81f71c48dbadfd78c98e0b54de27", size = 865410, upload-time = "2025-09-16T16:38:16.279Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/8f/a1d0402d52e5ebd14283718abefdc0c16f308cf10bee56cdff04b1f5119b/pymongo-4.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:da0a13f345f4b101776dbab92cec66f0b75015df0b007b47bd73bfd0305cc56a", size = 865695, upload-time = "2025-09-16T16:38:18.015Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/38/d1ef69028923f86fd00638d9eb16400d4e60a89eabd2011fe631fd3186cf/pymongo-4.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9481a492851e432122a83755d4e69c06aeb087bbf8370bac9f96d112ac1303fd", size = 1434758, upload-time = "2025-09-16T16:38:20.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/eb/a8d5dff748a2dd333610b2e4c8120b623e38ea2b5e30ad190d0ce2803840/pymongo-4.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:625dec3e9cd7c3d336285a20728c01bfc56d37230a99ec537a6a8625af783a43", size = 1485716, upload-time = "2025-09-16T16:38:21.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/d4/17ba457a828b733182ddc01a202872fef3006eed6b54450b20dc95a2f77d/pymongo-4.15.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26a31af455bffcc64537a7f67e2f84833a57855a82d05a085a1030c471138990", size = 1460160, upload-time = "2025-09-16T16:38:23.509Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/25/42b8662c09f5ca9c81d18d160f48e58842e0fa4c314ea02613c5e5d54542/pymongo-4.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea4415970d2a074d5890696af10e174d84cb735f1fa7673020c7538431e1cb6e", size = 1439284, upload-time = "2025-09-16T16:38:25.248Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/bb/46b9d978161828eb91973bd441a3f05f73c789203e976332a8de2832d5db/pymongo-4.15.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51ee050a2e026e2b224d2ed382830194be20a81c78e1ef98f467e469071df3ac", size = 1407933, upload-time = "2025-09-16T16:38:27.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/55/bd5af98f675001f4b06f7314b3918e45809424a7ad3510f823f6703cd8f2/pymongo-4.15.1-cp311-cp311-win32.whl", hash = "sha256:9aef07d33839f6429dc24f2ef36e4ec906979cb4f628c57a1c2676cc66625711", size = 844328, upload-time = "2025-09-16T16:38:28.513Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/78/90989a290dd458ed43a8a04fa561ac9c7b3391f395cdacd42e21f0f22ce4/pymongo-4.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ea6e5ff4d6747e7b64966629a964db3089e9c1e0206d8f9cc8720c90f5a7af1", size = 858951, upload-time = "2025-09-16T16:38:30.074Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/bb/d4d23f06e166cd773f2324cff73841a62d78a1ad16fb799cf7c5490ce32c/pymongo-4.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:bb783d9001b464a6ef3ee76c30ebbb6f977caee7bbc3a9bb1bd2ff596e818c46", size = 848290, upload-time = "2025-09-16T16:38:31.741Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/31/bc4525312083706a59fffe6e8de868054472308230fdee8db0c452c2b831/pymongo-4.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bab357c5ff36ba2340dfc94f3338ef399032089d35c3d257ce0c48630b7848b2", size = 920261, upload-time = "2025-09-16T16:38:33.614Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/55/4d99aec625494f21151b8b31e12e06b8ccd3b9dcff609b0dd1acf9bbbc0e/pymongo-4.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46d1af3eb2c274f07815372b5a68f99ecd48750e8ab54d5c3ff36a280fb41c8e", size = 919956, upload-time = "2025-09-16T16:38:35.121Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/60/8f1afa41521df950e13f6490ecdef48155fc63b78f926e7649045e07afd1/pymongo-4.15.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7dc31357379318881186213dc5fc49b62601c955504f65c8e72032b5048950a1", size = 1698596, upload-time = "2025-09-16T16:38:36.586Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/3f/e48d50ee8d6aa0a4cda7889dd73076ec2ab79a232716a5eb0b9df070ffcf/pymongo-4.15.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12140d29da1ecbaefee2a9e65433ef15d6c2c38f97bc6dab0ff246a96f9d20cd", size = 1762833, upload-time = "2025-09-16T16:38:38.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/87/db976859efc617f608754e051e1468459d9a818fe1ad5d0862e8af57720b/pymongo-4.15.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf193d2dcd91fa1d1dfa1fd036a3b54f792915a4842d323c0548d23d30461b59", size = 1731875, upload-time = "2025-09-16T16:38:39.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/59/3643ad52a5064ad3ef8c32910de6da28eb658234c25f2db5366f16bffbfb/pymongo-4.15.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2c0bdcf4d57e4861ed323ba430b585ad98c010a83e46cb8aa3b29c248a82be1", size = 1701853, upload-time = "2025-09-16T16:38:41.333Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/96/441c190823f855fc6445ea574b39dca41156acf723c5e6a69ee718421700/pymongo-4.15.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43fcfc19446e0706bbfe86f683a477d1e699b02369dd9c114ec17c7182d1fe2b", size = 1660978, upload-time = "2025-09-16T16:38:42.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/49/bd7e783fb78aaf9bdaa3f88cc238449be5bc5546e930ec98845ef235f809/pymongo-4.15.1-cp312-cp312-win32.whl", hash = "sha256:e5fedea0e7b3747da836cd5f88b0fa3e2ec5a394371f9b6a6b15927cfeb5455d", size = 891175, upload-time = "2025-09-16T16:38:44.658Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/28/7de5858bdeaa07ea4b277f9eb06123ea358003659fe55e72e4e7c898b321/pymongo-4.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:330a17c1c89e2c3bf03ed391108f928d5881298c17692199d3e0cdf097a20082", size = 910619, upload-time = "2025-09-16T16:38:46.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/87/c39f4f8415e7c65f8b66413f53a9272211ff7dfe78a5128b27027bf88864/pymongo-4.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:756b7a2a80ec3dd5b89cd62e9d13c573afd456452a53d05663e8ad0c5ff6632b", size = 896229, upload-time = "2025-09-16T16:38:48.563Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pymsalruntime"
|
||||
version = "0.18.1"
|
||||
@@ -5426,15 +5223,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pyopenssl"
|
||||
version = "26.2.0"
|
||||
version = "26.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1a/51/27a5ad5f939d08f690a326ef9582cda7140555180db71695f6fb747d6a36/pyopenssl-26.2.0.tar.gz", hash = "sha256:8c6fcecd1183a7fc897548dfe388b0cdb7f37e018200d8409cf33959dbe35387", size = 182195, upload-time = "2026-05-04T23:06:09.72Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/11/a62e1d33b373da2b2c2cd9eb508147871c80f12b1cacde3c5d314922afdd/pyopenssl-26.0.0.tar.gz", hash = "sha256:f293934e52936f2e3413b89c6ce36df66a0b34ae1ea3a053b8c5020ff2f513fc", size = 185534, upload-time = "2026-03-15T14:28:26.353Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/73/b8/a0e2790ae249d6f38c9f66de7a211621a7ab2650217bcd04e1262f578a56/pyopenssl-26.2.0-py3-none-any.whl", hash = "sha256:4f9d971bc5298b8bc1fab282803da04bf000c755d4ad9d99b52de2569ca19a70", size = 55823, upload-time = "2026-05-04T23:06:08.395Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/7d/d4f7d908fa8415571771b30669251d57c3cf313b36a856e6d7548ae01619/pyopenssl-26.0.0-py3-none-any.whl", hash = "sha256:df94d28498848b98cc1c0ffb8ef1e71e40210d3b0a8064c9d29571ed2904bf81", size = 57969, upload-time = "2026-03-15T14:28:24.864Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5759,18 +5556,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests-toolbelt"
|
||||
version = "1.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requestsexceptions"
|
||||
version = "1.4.0"
|
||||
@@ -5989,37 +5774,6 @@ dependencies = [
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c5/06/c6dcc975a1e7d89bc764fd271da8138b318e18080b48e7f1acd2ab63df28/shodan-1.31.0.tar.gz", hash = "sha256:c73275386ea02390e196c35c660706a28dd4d537c5a21eb387ab6236fac251f6", size = 57939, upload-time = "2023-12-17T01:42:02.426Z" }
|
||||
|
||||
[[package]]
|
||||
name = "simplejson"
|
||||
version = "4.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0e/2a/54837395a3487c725669428d513293612a48d82b95a0642c936932e5d898/simplejson-4.1.1.tar.gz", hash = "sha256:c08eb9f7a90f77ae470e19a07472e9a79ebc0d1c2315d86a72767665bd5ba79f", size = 118860, upload-time = "2026-04-24T19:24:59.819Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/25/39013ffe279d90093ec1c848565b3683c586906c10fa55d9000ec29d046b/simplejson-4.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2867c64d92abd1992c15666fae198203093f593e43d6b81adf176bae530d493a", size = 111538, upload-time = "2026-04-24T19:22:49.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/ae/2c272971c8a87e2539c54a98eb6ff037bee1e2e93943c3986cf7500a4f3a/simplejson-4.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c47c46e16c8ea9e4850061e6ed5aa2b9cd2074cb2274bfd9c138cba15ce7453", size = 90594, upload-time = "2026-04-24T19:22:50.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/a2/6eebfb99dedc139f549200f61ade6d1890ac5707c5d427bdfa6fe39c9313/simplejson-4.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e294e33dbf316a9bbdd4030d46503c9b0f19470ae7ad6af5bae6c426bc2e869f", size = 90718, upload-time = "2026-04-24T19:22:51.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/7e/c9e6c0c4ad8415e64dad0c47f619b556b02680a41631b4dbc281d55dc54d/simplejson-4.1.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7ce252b28fddbdd83db5bd7d93dad2a8a591d7ada098afec9c1b23d6b722a7a4", size = 180901, upload-time = "2026-04-24T19:22:53.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/09/69e331e3994b1ed9be6ce9ace4ade704e7ed503edf869929ca7bb404eda8/simplejson-4.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c44ef6b02a4eb67ed17a72342341792149b3ff46f15426c26e970e49addf327", size = 178133, upload-time = "2026-04-24T19:22:54.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/40/ed806f24afef295c1032448f5ff6f6f2979392d5645ddb9f4fed7f38194d/simplejson-4.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82bfca2b85a34178c25829c703f0a9e9f113a5af7539285bd3efb583a0bf1ba3", size = 188155, upload-time = "2026-04-24T19:22:56.044Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/94/8d6f515b827b0f7881a49c8c1ac6920b7ae9428939ef04238c973278b42a/simplejson-4.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0e4b23f71dd781f8830f1663dc01a4944d3dbf87a1f93d78fba1cf64722d0ccf", size = 176225, upload-time = "2026-04-24T19:22:57.981Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/fd/6dffb4956563d48bbe46b91ff341adae34920e94008fd6b8d728072abfc7/simplejson-4.1.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:82fee635d7b73ad801030b05a75fbd34a098da0c2ecf600667a03636d09e1e42", size = 185535, upload-time = "2026-04-24T19:22:59.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/d2/a509ee37763e79aec75d68f8521db1440306edeba3b8b4064ab4ee8bf1d9/simplejson-4.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:68e62eda21192c5ea9bb92d571ca46a4477fef48762f50d433de2b4253051551", size = 179302, upload-time = "2026-04-24T19:23:01.324Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/23/5b343bfd2a79d3b6818e4db3586c405a001a090d4c89d336e31273ce7177/simplejson-4.1.1-cp311-cp311-win32.whl", hash = "sha256:ffd3d82294b47f5ec64050021ace95fd62628a0c1cc8bbf4d06d2d1fb697e055", size = 88408, upload-time = "2026-04-24T19:23:02.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/04/df9b37aedbd524dca20840d25ebe01d6ae486b89792aeff5d15b9c4114f7/simplejson-4.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:78a3fe0995be42bed62a26aa78e0e0b4d87c6545785346b9cc898f3389569a35", size = 90526, upload-time = "2026-04-24T19:23:04.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/25/e90998fe8e480eb43b966c09e835379887d427567ebd496563d3b1e16b19/simplejson-4.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:19040a17154dc03d289bab68d73ce0a6a0be01de30c584bbdd93490bead14b22", size = 112414, upload-time = "2026-04-24T19:23:06.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/a0/abd4785f36c3400f1fbb21f517be39295a750a714f04b7ee175adf6ef580/simplejson-4.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a94ebaecdbaa80d9551a3ec6bf0c9302fc8b53ab6c1b2bfd498a1df4cb28158d", size = 91120, upload-time = "2026-04-24T19:23:07.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/78/fc060d2e3b13c6ec59288574b8efac64075e316b2afba4396a56b2422f78/simplejson-4.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:67341c95c0a168ab4a6d1e807e50463f1c8da932c3286d81e201266c427061fa", size = 91055, upload-time = "2026-04-24T19:23:09.264Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/b6/156a8de1e1b47694f0e7de6675866936608d45dc68388fd017d36f8693be/simplejson-4.1.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:45ec18e337fec538b7e902d489505c450b2454653d1290f3f50385e6fd8aa607", size = 190297, upload-time = "2026-04-24T19:23:11.226Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/1c/e4d0eab695be3eb21d0f46bce820752031f03e7113f9c80a9b3c73ee7157/simplejson-4.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:820c69a4710400e9b248d5670647d60be58824369282d3925e516b3ff1a7cd82", size = 187002, upload-time = "2026-04-24T19:23:12.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/0e/7f5a59d29426b062d5928fb88b403c3f797129d53be7102f955dbe51aa44/simplejson-4.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e708d373a10e4378ef2d59f8361850c7150fd907ed49efe49bc5492160476d1", size = 195146, upload-time = "2026-04-24T19:23:14.517Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/18/9943db224dd4d5fa3c090c3e56a94c37b254338c83995ec5680285111c40/simplejson-4.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:980fc33353f81fd12d8c49d44f8c2760d1dc8192285e627c5180d141035b228a", size = 183931, upload-time = "2026-04-24T19:23:16.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/08/9a690da9a766161c06c627d805362cf159f1abe480969372b2897649b955/simplejson-4.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:de2ed102fff88dacf543699f53ee3a533cc11539a39baa176b7e09dd783069d6", size = 192228, upload-time = "2026-04-24T19:23:18.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/88/bd8aad36b451ffb0e0a3f721d695a88befa6d1ac7d1e02ae788ca7ff4029/simplejson-4.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2785ff8edc0e28bf773a32543a6bbed46351453c997b3f6709c744e3c2f7eabb", size = 187808, upload-time = "2026-04-24T19:23:21.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/ee/14f91db0d1f481533b651dafbf8cd0da088d9817f7af30c68f7f19f9c847/simplejson-4.1.1-cp312-cp312-win32.whl", hash = "sha256:2e0d5ead6d14610467ec356ec1f6b5d8a56aa216abaad8d41c8b873b16cf313f", size = 88512, upload-time = "2026-04-24T19:23:22.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/c4/90de06b2d8737c68c05ff9274113f854dbf6a5f28b7a955212111672cb57/simplejson-4.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:63a5451f557d6be48a231bae932458655c620902b868170b2f1c8afed496f6b4", size = 90748, upload-time = "2026-04-24T19:23:24.494Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/6a/8b74c52ffd33dbbde00fe7251fee6a0acdc8cea33f7a43805aed258fb79b/simplejson-4.1.1-py3-none-any.whl", hash = "sha256:2ce92b3748f02423e26d2bfb636fb9d7a8f67c8f5854dcae69d350d123b2eee2", size = 69195, upload-time = "2026-04-24T19:24:57.962Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
@@ -6449,16 +6203,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "workos"
|
||||
version = "8.3.0"
|
||||
version = "6.0.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
{ name = "httpx" },
|
||||
{ name = "pyjwt", extra = ["crypto"] },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/f6/bb27fe77e70b5e2c5da72500ca0ece8b0e8318010fec92c31d68483314e3/workos-8.3.0.tar.gz", hash = "sha256:07b66c2fb287adb593e4d77a2e6cb05b48bd8ff0b2722f343d18eeb5e14f7472", size = 201587, upload-time = "2026-06-30T15:19:22.834Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ca/0d/0a7f78912657f99412c788932ea1f3f4089916e77bdef7d2463842febe08/workos-6.0.8.tar.gz", hash = "sha256:43aa3f1992a0a4ca8933d9b6e5ada846dd3b1fe0ee10e64c876ee2000fc6090d", size = 178137, upload-time = "2026-04-24T18:48:03.203Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/ed/7e6fe07c5bc0222fd92c1cf1f3c4c24293e4e5fc7bb5d7df90e4ee61c17f/workos-8.3.0-py3-none-any.whl", hash = "sha256:d0fa842b93bfc5fb33bf49e69cf8c379936cf54b87c6e2f50bcc6dd2e84f8fe4", size = 592275, upload-time = "2026-06-30T15:19:21.333Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/3f/3d96da80d650b2f97d58af626053354584f619dbb769051e118bd9cd1ca5/workos-6.0.8-py3-none-any.whl", hash = "sha256:a00dd4930333aded2babbba824f8032eea05c5ca8c44d04a3fa068cf6be6e21a", size = 524505, upload-time = "2026-04-24T18:48:01.389Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -6,19 +6,12 @@ component_management:
|
||||
- component_id: "api"
|
||||
paths:
|
||||
- "api/**"
|
||||
- component_id: "mcp_server"
|
||||
paths:
|
||||
- "mcp_server/**"
|
||||
|
||||
flags:
|
||||
api:
|
||||
paths:
|
||||
- "api/**"
|
||||
carryforward: true
|
||||
mcp:
|
||||
paths:
|
||||
- "mcp_server/**"
|
||||
carryforward: true
|
||||
|
||||
comment:
|
||||
layout: "header, diff, flags, components"
|
||||
|
||||
@@ -18,12 +18,15 @@ spec:
|
||||
triggers:
|
||||
- type: {{ .Values.worker.keda.triggerType }}
|
||||
metadata:
|
||||
userName: {{ .Values.worker.keda.postgresql.userName | quote }}
|
||||
passwordFromEnv: {{ .Values.worker.keda.postgresql.passwordFromEnv | quote }}
|
||||
host: {{ .Values.worker.keda.postgresql.host | default (printf "%s-postgresql.%s.svc.cluster.local" .Release.Name .Release.Namespace) | quote }}
|
||||
port: {{ .Values.worker.keda.postgresql.port | quote }}
|
||||
dbName: {{ .Values.worker.keda.postgresql.database | default .Values.postgresql.auth.database | quote }}
|
||||
sslmode: {{ .Values.worker.keda.postgresql.sslmode | quote }}
|
||||
query: {{ .Values.worker.keda.query | quote }}
|
||||
targetQueryValue: {{ .Values.worker.keda.targetQueryValue | quote }}
|
||||
userName: "postgres"
|
||||
passwordFromEnv: POSTGRES_ADMIN_PASSWORD
|
||||
host: {{ .Release.Name }}-postgresql
|
||||
port: {{ .Values.postgresql.port | quote }}
|
||||
dbName: {{ .Values.postgresql.auth.database | quote }}
|
||||
sslmode: disable
|
||||
# Query for KEDA to count the number of scans that are in executing, available, or scheduled states,
|
||||
# where the scheduled time is within the last 2 hours and is before NOW(). Used for scaling workers.
|
||||
query: >-
|
||||
SELECT COUNT(*) FROM scans WHERE ((state='executing' OR state='available' OR state='scheduled') and scheduled_at < NOW() and scheduled_at > NOW() - INTERVAL '2 hours')
|
||||
targetQueryValue: "1"
|
||||
{{- end }}
|
||||
|
||||
@@ -427,61 +427,10 @@ worker:
|
||||
pollingInterval: 30
|
||||
# -- The cooldown period in seconds for scaling
|
||||
cooldownPeriod: 120
|
||||
# -- The KEDA scaler type. Only `postgresql` is supported by the default query below.
|
||||
# -- The trigger type for scaling (cpu or memory)
|
||||
triggerType: "postgresql"
|
||||
# PostgreSQL connection used by the scaler query. The KEDA operator opens this
|
||||
# connection from its own namespace, so `host` must resolve from there. The
|
||||
# defaults target the bundled postgresql subchart; set them explicitly when
|
||||
# using an external database (postgresql.enabled: false).
|
||||
postgresql:
|
||||
# -- Scaler database host. Defaults to the bundled "<release>-postgresql.<namespace>.svc.cluster.local" service.
|
||||
host: ""
|
||||
# -- Scaler database port.
|
||||
port: "5432"
|
||||
# -- Scaler database name. Defaults to `postgresql.auth.database`.
|
||||
database: ""
|
||||
# -- User the scaler authenticates as.
|
||||
userName: "postgres"
|
||||
# -- Name of an env var on the worker container holding the password.
|
||||
passwordFromEnv: "POSTGRES_ADMIN_PASSWORD"
|
||||
# -- sslmode for the scaler connection.
|
||||
sslmode: "disable"
|
||||
# -- The scaler divides the query result by this value to get the desired replica count.
|
||||
targetQueryValue: "1"
|
||||
# -- Query the scaler runs to measure pending work. It replaces the previous
|
||||
# 2-hour scheduled-only window, which missed manual scans, older backlogs and
|
||||
# in-progress scans. Override to tune scaling for your workload.
|
||||
#
|
||||
# The default sums three signals:
|
||||
# 1. Scans executing or available, bounded to rows updated in the last 24h so
|
||||
# orphaned rows do not pin the worker up, plus scheduled scans that are due
|
||||
# (no lower bound, so an overdue backlog still scales up).
|
||||
# 2. Scan tasks published in the last 48h that no worker has finished. A PENDING
|
||||
# TaskResult is written at publish time (before_task_publish in api/signals.py),
|
||||
# so Beat's daily publishes are visible even with zero workers. Signal 1 alone
|
||||
# deadlocks with minReplicas 0: every scan row after the first is created by
|
||||
# the worker, so once the initial row ages out of the 24h bound there is
|
||||
# nothing to count and nothing to create more.
|
||||
# 3. Non-scan tasks pending in the last hour. Provider connection checks,
|
||||
# deletions, reports and backfills never touch the scans table, so without
|
||||
# this they are never picked up while the worker is scaled to zero.
|
||||
# This includes reconcile-orphan-tasks, a Beat watchdog that runs every two
|
||||
# minutes, so with minReplicas 0 the worker is woken about that often. Add
|
||||
# it to the excluded task names below, or raise cooldownPeriod, if you would
|
||||
# rather trade watchdog latency for longer idle periods.
|
||||
query: >-
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM scans
|
||||
WHERE (state IN ('executing', 'available') AND updated_at > NOW() - INTERVAL '24 hours')
|
||||
OR (state = 'scheduled' AND scheduled_at < NOW()))
|
||||
+ (SELECT COUNT(*) FROM django_celery_results_taskresult
|
||||
WHERE task_name IN ('scan-perform', 'scan-perform-scheduled')
|
||||
AND status IN ('PENDING', 'RECEIVED', 'STARTED')
|
||||
AND date_created > NOW() - INTERVAL '48 hours')
|
||||
+ (SELECT COUNT(*) FROM django_celery_results_taskresult
|
||||
WHERE task_name NOT IN ('scan-perform', 'scan-perform-scheduled')
|
||||
AND status IN ('PENDING', 'RECEIVED', 'STARTED')
|
||||
AND date_created > NOW() - INTERVAL '1 hour')
|
||||
# -- The target utilization percentage for the worker pods
|
||||
value: "50"
|
||||
|
||||
worker_beat:
|
||||
# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
|
||||
|
||||
@@ -64,7 +64,7 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777
|
||||
image: postgres:16.3-alpine3.20@sha256:36ed71227ae36305d26382657c0b96cbaf298427b3f1eaeb10d77a6dea3eec41
|
||||
hostname: "postgres-db"
|
||||
volumes:
|
||||
- ./_data/postgres:/var/lib/postgresql/data
|
||||
@@ -88,7 +88,7 @@ services:
|
||||
retries: 5
|
||||
|
||||
valkey:
|
||||
image: valkey/valkey:8-alpine@sha256:a038175878d66b9d274fbf8be73c0305e93798b83917647f167e18cef3c71eec
|
||||
image: valkey/valkey:7-alpine3.19@sha256:4054fe7fc607b9326ac7c4691ed26e9670d2ff17a9fb28c2577adecf928acbcc
|
||||
hostname: "valkey"
|
||||
volumes:
|
||||
- ./_data/valkey:/data
|
||||
@@ -104,7 +104,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
neo4j:
|
||||
image: graphstack/dozerdb:5.26.27.0@sha256:9b54d6b3a98a76c00bd23e8e78d8c82081ff168162aebd47b25c234e092cb0a0
|
||||
image: graphstack/dozerdb:5.26.3.0@sha256:a77526ea3918fdc46d1fff70c4aea7d71d3874a26ecec059179d6775845b1247
|
||||
hostname: "neo4j"
|
||||
volumes:
|
||||
- ./_data/neo4j:/data
|
||||
|
||||
+3
-3
@@ -60,7 +60,7 @@ services:
|
||||
start_period: 60s
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777
|
||||
image: postgres:16.3-alpine3.20@sha256:36ed71227ae36305d26382657c0b96cbaf298427b3f1eaeb10d77a6dea3eec41
|
||||
hostname: "postgres-db"
|
||||
volumes:
|
||||
- ./_data/postgres:/var/lib/postgresql/data
|
||||
@@ -80,7 +80,7 @@ services:
|
||||
retries: 5
|
||||
|
||||
valkey:
|
||||
image: valkey/valkey:8-alpine@sha256:a038175878d66b9d274fbf8be73c0305e93798b83917647f167e18cef3c71eec
|
||||
image: valkey/valkey:7-alpine3.19@sha256:4054fe7fc607b9326ac7c4691ed26e9670d2ff17a9fb28c2577adecf928acbcc
|
||||
hostname: "valkey"
|
||||
volumes:
|
||||
- ./_data/valkey:/data
|
||||
@@ -96,7 +96,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
neo4j:
|
||||
image: graphstack/dozerdb:5.26.27.0@sha256:9b54d6b3a98a76c00bd23e8e78d8c82081ff168162aebd47b25c234e092cb0a0
|
||||
image: graphstack/dozerdb:5.26.3.0@sha256:a77526ea3918fdc46d1fff70c4aea7d71d3874a26ecec059179d6775845b1247
|
||||
hostname: "neo4j"
|
||||
volumes:
|
||||
- ./_data/neo4j:/data
|
||||
|
||||
+1
-338
@@ -1,346 +1,9 @@
|
||||
---
|
||||
title: "Changelog"
|
||||
description: "Track new features, improvements, provider updates, and bug fixes shipped in each Prowler release across CLI, App, Cloud, and MCP products."
|
||||
description: "New features and improvements in each Prowler release"
|
||||
rss: true
|
||||
---
|
||||
|
||||
<Update label="v5.39.0" description="August 13, 2026">
|
||||
### 🤖 Lighthouse AI — Finding Skills
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
Lighthouse AI now embeds a Skills menu on every finding, answering the questions an analyst actually asks. **Contextual Fix** produces the fix for the finding, **Triage Decision** judges whether it is real and closes it out when it is not, and **Systemic Scope** determines whether the problem is a one-off or everywhere. A free-form "Ask Lighthouse anything" prompt sits in the same menu, and each run shows its progress and offers follow-up actions such as creating a Jira issue or muting the finding.
|
||||
|
||||

|
||||
|
||||
Read more in the [Lighthouse AI documentation](https://docs.prowler.com/getting-started/products/prowler-cloud-lighthouse).
|
||||
|
||||
### ☁️ Azure Management Group Onboarding
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
Azure subscriptions no longer onboard one at a time. Choose "Add Multiple Subscriptions With Azure Management Group" in the add-provider wizard, enter the Microsoft Entra tenant ID, and authenticate once with a single tenant-wide service principal: Prowler discovers the entire management-group hierarchy under the tenant root, lets you select the subscriptions to onboard, and creates their providers with the management-group structure preserved. Azure now matches the one-step onboarding that AWS Organizations and GCP organizations already have.
|
||||
|
||||

|
||||
|
||||
Read more in the [Azure Management Groups documentation](https://docs.prowler.com/user-guide/tutorials/prowler-cloud-azure-management-groups).
|
||||
|
||||
### ✅ Findings Triage — Verify MANUAL Findings as PASS
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
Checks that require human judgment report `MANUAL` findings. For these findings, and only for them, the triage status selector now offers **Resolved**: choosing it asks for the required written evidence and verifies the finding as passing. The finding then reports an effective `PASS` while preserving the raw `MANUAL` scan result, across findings, finding groups, compliance reports, and scans, with the attestation's author, evidence, and validity always visible. Attestations expire automatically after 90 days, or as soon as a new scan reports a real failure, returning the finding to the review queue.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Read more in the [Findings Triage documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-findings-triage#verify-a-manual-finding-as-pass).
|
||||
|
||||
### ☁️ Prowler Cloud MCP — Organizations Management and Grouped Jira Dispatch
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
The hosted Prowler Cloud MCP server adds eight organization tools, so an agent can onboard and manage entire cloud organizations end to end: create the organization, discover its accounts, subscriptions, and projects, apply the selection, and manage the resulting providers. The tools cover AWS Organizations, GCP organizations, and Azure tenant root management groups, and they are available to Lighthouse AI.
|
||||
|
||||
`prowler_send_findings_to_jira` also gains Cloud-only dispatch capabilities: select failed findings by check IDs against the latest completed scan, and send them in grouped mode, one Jira work item per check listing up to 50 affected resources, with per-group error reporting.
|
||||
|
||||
Read more in the [Prowler MCP tools documentation](https://docs.prowler.com/getting-started/basic-usage/prowler-mcp-tools) and its [Jira operations reference](https://docs.prowler.com/getting-started/basic-usage/prowler-mcp-tools#jira-operations).
|
||||
|
||||
### 🕸️ Attack Paths — Grouped Graph with Outcome Destinations
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
The Attack Paths graph now reads from source to destination. Resources of the same class collapse into a single expandable node with a count, clicking reveals its members, and every path terminates in an explicit outcome node naming the destination impact: code execution, privilege escalation, public exposure, or resource inventory. The per-account hub node is gone, and the clicked resource stays highlighted while its findings are expanded.
|
||||
|
||||

|
||||
|
||||
Explore the full Attack Paths query catalog at [Prowler Hub](https://hub.prowler.com/attack-paths).
|
||||
|
||||
Read more in the [Attack Paths documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-attack-paths).
|
||||
|
||||
### 📚 New Compliance Framework — CMMC 2.0
|
||||
|
||||
The Cybersecurity Maturity Model Certification (CMMC) is the certification the US Department of Defense requires from contractors and suppliers that handle federal contract data. Prowler now includes CMMC 2.0 as a universal framework with all 149 requirements defined by the CMMC Program rule (32 CFR Part 170), organized in its three levels:
|
||||
|
||||
- **Level 1 (Foundational):** 15 requirements for the basic safeguarding of Federal Contract Information, from FAR 52.204-21.
|
||||
- **Level 2 (Advanced):** 110 requirements from NIST SP 800-171 Rev 2, protecting Controlled Unclassified Information.
|
||||
- **Level 3 (Expert):** 24 enhanced requirements from NIST SP 800-172 for the most sensitive programs.
|
||||
|
||||
Requirements map to Prowler checks across AWS, Azure, GCP, Alibaba Cloud, Oracle Cloud, and Microsoft 365, so one framework reports the compliance posture of the whole estate.
|
||||
|
||||
Read more in the [Compliance documentation](https://docs.prowler.com/user-guide/compliance/tutorials/compliance).
|
||||
|
||||
### 🔍 Checks
|
||||
|
||||
#### Microsoft 365
|
||||
|
||||
Twenty new Entra ID checks expand the coverage of CIS Microsoft 365 Foundations Benchmark v7.0.0:
|
||||
|
||||
- **Password protection:** custom banned password list, on-premises enforcement, and lockout threshold and duration.
|
||||
- **Default user permissions:** security group and Microsoft 365 group creation restricted, and guest invitations limited to allowed domains.
|
||||
- **Conditional Access:** high and medium sign-in risk blocked, authentication transfer blocked, untrusted locations blocked, trusted named locations defined, sign-in frequency enforced, and token protection enforced.
|
||||
- **Sessions and authentication methods:** idle session timeout configured, email one-time passcodes disabled, and Microsoft Authenticator context shown.
|
||||
- **PIM and access reviews:** approval required to activate the Global Administrator and Privileged Role Administrator roles, and access reviews configured for guest users and privileged roles.
|
||||
|
||||
Explore all Microsoft 365 checks at [Prowler Hub](https://hub.prowler.com/check?provider=m365).
|
||||
|
||||
#### AWS
|
||||
|
||||
Two new checks detect hardcoded secrets:
|
||||
|
||||
- `batch_job_definition_no_secrets` scans Batch job definition environment variables and command parameters. Thanks to @praneetrajv!
|
||||
- `awslambda_layer_no_secrets_in_content` scans Lambda layer package content. Thanks to @ganiganesh25!
|
||||
|
||||
Explore all AWS checks at [Prowler Hub](https://hub.prowler.com/check?provider=aws).
|
||||
|
||||
### 🙌 External Contributors
|
||||
|
||||
Thank you to our community contributors for this release!
|
||||
|
||||
- @praneetrajv: AWS `batch_job_definition_no_secrets` check ([#12117](https://github.com/prowler-cloud/prowler/pull/12117))
|
||||
- @ganiganesh25: AWS `awslambda_layer_no_secrets_in_content` check ([#12233](https://github.com/prowler-cloud/prowler/pull/12233))
|
||||
- @andoniaf: GitHub `organization_repository_creation_limited` now reports low severity when repository creation is limited to private or internal visibility ([#12164](https://github.com/prowler-cloud/prowler/pull/12164))
|
||||
|
||||
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.39.0) for the complete list of changes.
|
||||
</Update>
|
||||
|
||||
<Update label="v5.38.0" description="August 6, 2026">
|
||||
### 📌 Compliance Watchlist
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
Compliance Watchlist keeps the frameworks an organization tracks in one shared list. Pin frameworks from any compliance view, manage several at once through a searchable catalog, and filter the Compliance section to show only the pinned frameworks.
|
||||
|
||||
The Overview page now reports the latest score for every pinned framework, while finding details highlight the watched frameworks associated with each check. Universal frameworks remain a single watchlist entry across provider views, keeping the organization's priorities consistent everywhere.
|
||||
|
||||

|
||||
|
||||
Read more in the [Compliance Watchlist documentation](https://docs.prowler.com/user-guide/compliance/tutorials/compliance#tracking-frameworks-with-the-compliance-watchlist).
|
||||
|
||||
### 🔐 SAML SSO - Multiple Email Domains
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
One SAML configuration can now authorize a primary email domain and up to 19 additional domains through the same Identity Provider. Every domain shares one stable Assertion Consumer Service (ACS) URL based on the primary domain, so subsidiaries, acquired companies, regional domains, and multiple brands no longer require separate tenants or duplicated SAML applications.
|
||||
|
||||
Domain ownership remains tenant-bound throughout the authentication flow. During service provider-initiated sign-in, the discovery domain and the domain asserted by the Identity Provider must resolve to the same tenant before provisioning continues.
|
||||
|
||||

|
||||
|
||||
Read more in the [SAML SSO documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-sso#add-multiple-saml-domains).
|
||||
|
||||
### 👥 User Sign-In Methods
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
The Users table now shows each account's sign-in methods as tags, including email/password, Google, GitHub, SAML with linked domains, and Partner SSO. Accounts without a reported method display a placeholder.
|
||||
|
||||

|
||||
|
||||
### 🕸️ Attack Paths - Expanded AWS Privilege-Escalation Coverage
|
||||
|
||||
Attack Paths adds 20 AWS privilege-escalation queries from [pathfinding.cloud](https://pathfinding.cloud), while `iam_policy_allows_privilege_escalation` gains 22 additional escalation combinations.
|
||||
|
||||
The new coverage includes service `iam:PassRole` paths across AWS Batch, Braket, Cognito Identity, ECS, EMR, EMR Serverless, GameLift, Glue, EC2 Image Builder, Kinesis Analytics, HealthOmics, EventBridge Scheduler, Systems Manager, and Step Functions. It also covers existing-resource abuse, permissions-boundary removal, role assumption, and IAM Identity Center permission-set policy injection.
|
||||
|
||||
The query catalog now exposes each AWS query's outcome category, distinguishing code execution, privilege escalation, public exposure, and resource inventory.
|
||||
|
||||
Explore the full Attack Paths query catalog at [Prowler Hub](https://hub.prowler.com/attack-paths).
|
||||
|
||||
Read more in the [Attack Paths documentation](https://docs.prowler.com/user-guide/tutorials/prowler-app-attack-paths).
|
||||
|
||||
### 🔍 Checks
|
||||
|
||||
#### Microsoft 365
|
||||
|
||||
Twelve new checks expand the coverage of CIS Microsoft 365 Foundations Benchmark v7.0.0:
|
||||
|
||||
- **Admin Center:** Shared Bookings is disabled.
|
||||
- **Defender:** Priority account protection and strict preset security policies are enabled.
|
||||
- **Entra ID:** Six checks cover device registration restrictions, local administrator behavior, device limits, LAPS, and BitLocker key visibility.
|
||||
- **Exchange Online:** Personal accounts in Outlook on the web are disabled and Direct Send is rejected.
|
||||
- **Microsoft Teams:** External access from trial-only tenants is blocked.
|
||||
|
||||
Explore all Microsoft 365 checks at [Prowler Hub](https://hub.prowler.com/check?provider=m365).
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- Prowler API, UI, SDK, and MCP container images now publish per-architecture Software Bills of Materials (SBOMs) and build-provenance attestations. Prowler Cloud production and Prowler Private Cloud images carry the same attestations.
|
||||
- SDK and API container builds verify the checksums of downloaded PowerShell, Trivy, and zizmor binaries before installation.
|
||||
- Grype now complements Trivy across the container-image security gates, detecting components and vulnerabilities that manifest-based scanners can miss and blocking fixable high and critical findings.
|
||||
- `aiohttp` was upgraded to 3.14.3 to address CVE-2026-69244. `cryptography` was upgraded to 50.0.0 to address CVE-2026-69247 and CVE-2026-69249.
|
||||
|
||||
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.38.0) for the complete list of changes.
|
||||
</Update>
|
||||
|
||||
<Update label="v5.37.0" description="August 3, 2026">
|
||||
### 💬 Lighthouse AI — Context-Aware Chat and a Bigger Toolbox
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
Lighthouse AI is now aware of your working context when in Prowler Cloud. Messages carry page-aware context — the page you are on, the finding or resource open in the side panel, and its metadata — so "explain this" just works, and each page offers concise contextual suggestions to start from.
|
||||
|
||||

|
||||
|
||||
Lighthouse also gained access to every tool family the Prowler MCP server advertises: scan configurations, scan scheduling, finding triage, alert rules and recipients, integrations, users, and roles. Every action remains gated by RBAC: Lighthouse AI can only do what the user asking could do themselves.
|
||||
|
||||
Read more in the [Lighthouse AI documentation](/getting-started/products/prowler-cloud-lighthouse).
|
||||
|
||||
### 🔌 Prowler MCP — Integrations, Users, and Roles
|
||||
|
||||
Prowler MCP gained three tool families, available on both the Cloud and the self-hosted Local MCP Server:
|
||||
|
||||
- **[Integrations](/getting-started/basic-usage/prowler-mcp-tools#integrations-management)** — manage where Prowler sends its results, with the full lifecycle for Amazon S3, AWS Security Hub, and Jira: create them, update credentials, configuration and attached providers, re-check connections, and delete them — plus turning findings into Jira work items directly from a conversation.
|
||||
- **[Users](/getting-started/basic-usage/prowler-mcp-tools#user-management)** — read-only tools to list the tenant users with their emails and identify the authenticated user.
|
||||
- **[Roles](/getting-started/basic-usage/prowler-mcp-tools#role-management)** — browse the RBAC roles defined in the tenant, inspect the capabilities each one grants, and set the role a user holds.
|
||||
|
||||
### ☁️ Prowler MCP — Cloud-Only Tools
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing). These tools are exposed only by the Cloud MCP Server at `https://mcp.prowler.com/mcp`; the self-hosted Local MCP Server **does not** include them.
|
||||
</Note>
|
||||
|
||||
A new `prowler_cloud_*` namespace adds 32 tools so your AI assistant can run Prowler Cloud workflows end to end instead of only reading from them:
|
||||
|
||||
- **[Alerts](/getting-started/basic-usage/prowler-mcp-tools#alerts)** — create and manage alert rules and email recipients, and browse the fired-alert history. Rule conditions can be dry-run before saving, so you can see what a rule would match without persisting anything.
|
||||
- **[Findings Triage](/getting-started/basic-usage/prowler-mcp-tools#findings-triage)** — set a finding's triage status and attach notes documenting the decision. Unlike muting, the finding stays visible.
|
||||
- **[Scan Scheduling](/getting-started/basic-usage/prowler-mcp-tools#scan-scheduling)** — configure daily, interval, weekly, or monthly recurring scans, one provider at a time or applied across many at once.
|
||||
- **[Scan Configurations](/getting-started/basic-usage/prowler-mcp-tools#scan-configurations)** — build reusable check and compliance selections and attach them to providers.
|
||||
|
||||
Read more in the [Prowler MCP tools reference](/getting-started/basic-usage/prowler-mcp-tools#prowler-cloud-tools).
|
||||
|
||||
### 🧭 Compliance — Grouped by provider of the same type
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
One framework, every provider, a single answer. Building on the cross-provider-type roll-up, the Compliance section now groups compliance for all providers of the same type: a **single-provider framework** — CIS AWS, CIS GCP, ENS for Azure — is aggregated across the latest completed scan of every provider of that type. Each framework card rolls up into a consolidated posture with a per-provider breakdown, a findings drill-down, and a combined executive PDF report. Requirement status follows the same strict precedence (FAIL over PASS over MANUAL), so one failing provider flags the requirement for the whole estate.
|
||||
|
||||

|
||||
|
||||
The Compliance tabs were also renamed to say what they aggregate: "Per Scan" is now **Single Scan**, "Cross-Provider" is now **Multiple Scans**, and Compliance lands on Multiple Scans by default.
|
||||
|
||||

|
||||
|
||||
Read more in the [Cross-Provider Compliance documentation](/user-guide/compliance/tutorials/cross-provider-compliance).
|
||||
|
||||
### ☁️ GCP Organization Onboarding
|
||||
|
||||
<Note>
|
||||
This feature is available exclusively in **Prowler Cloud** and **Prowler Private Cloud** with a [subscription](https://prowler.com/pricing).
|
||||
</Note>
|
||||
|
||||
Onboarding an entire Google Cloud organization is now a single guided flow. Provide an organization-level credential and Prowler discovers the full hierarchy, every folder and project. Pick the folders and projects to onboard from a selection tree, set custom aliases, test the connection, and launch: each selected project is registered as a provider, with no need to add them one by one. Post-onboarding management is covered too, including credential replacement and organization-wide deletion.
|
||||
|
||||
Read more in the [GCP Organizations documentation](/user-guide/tutorials/prowler-cloud-gcp-organizations).
|
||||
|
||||
### 🕸️ Attack Paths — More Privilege Escalation Queries
|
||||
|
||||
Attack Paths adds four AWS privilege-escalation detection queries from [pathfinding.cloud](https://pathfinding.cloud). Thanks to @paramanandmallik!
|
||||
|
||||
- **[STS-002](https://hub.prowler.com/attack-paths/aws-sts-privesc-cross-account-trust)** — cross-account role trust
|
||||
- **[STS-003](https://hub.prowler.com/attack-paths/aws-sts-privesc-wildcard-trust)** — wildcard role trust
|
||||
- **[IAM-022](https://hub.prowler.com/attack-paths/aws-iam-privesc-delete-user-permissions-boundary)** — user permissions-boundary removal
|
||||
- **[SSO-001](https://hub.prowler.com/attack-paths/aws-sso-privesc-permission-set-escalation)** — IAM Identity Center permission-set escalation
|
||||
|
||||
The query info panel now links every query to its page on [Prowler Hub](https://hub.prowler.com), and the IAM privilege-escalation queries were reworked to run efficiently on accounts with many IAM roles, users, or groups, fixing runtime errors and timeouts on large graphs.
|
||||
|
||||
Read more in the [Attack Paths documentation](/user-guide/tutorials/prowler-app-attack-paths).
|
||||
|
||||
### 🛡️ AWS Confidential Computing — Nitro Enclaves Checks
|
||||
|
||||
Prowler adds the first CSPM coverage for confidential computing workloads on AWS, with **11 new checks** for [Nitro Enclaves](https://aws.amazon.com/ec2/nitro/nitro-enclaves/), developed together with [Guillermo Ruiz](https://www.linkedin.com/in/gruizesteban/) from AWS.
|
||||
|
||||
- **Workload host environment (EC2)** — five `ec2_confidential_workload_host_*` checks for the parent instance: IMDSv2 not enforced, public IP exposure, unrestricted ingress, exposed vsock proxy ports, and hosts not running.
|
||||
- **KMS attestation policy** — six `kms_key_enclave_*` checks for the key policies gating enclave secrets: attestation not enforced or bypassable, missing deployment binding, debug-mode attestations, PCR mismatches, and unknown enclave images.
|
||||
|
||||
All checks are fully passive, using AWS APIs and CloudTrail with no instance access or SSM agent required, and are mapped across 23 compliance frameworks, including NIST 800-53 Rev 5, PCI-DSS v4.0, ISO 27001:2022, SOC 2, HIPAA, and MITRE ATT&CK.
|
||||
|
||||
Read more about it this [blog post](https://prowler.com/blog/your-llm-runs-in-a-nitro-enclave-who-is-checking-the-enclave).
|
||||
|
||||
Try them out now at [cloud.prowler.com](https://cloud.prowler.com/sign-up)!
|
||||
|
||||
### 🏢 New Provider — Huawei Cloud
|
||||
|
||||
Prowler now scans [**Huawei Cloud**](https://www.huaweicloud.com/), with **25 checks** across ten services: CTS, ECS, ELB, EVS, IAM, KMS, OBS, RDS, VPC, and WAF, plus the CIS Huawei Cloud Foundations Benchmark 1.0 compliance framework. Thanks to @tomitobio for their 1st provider in Prowler!
|
||||
|
||||
To scan a Huawei Cloud account, export the IAM user's access key credentials and run Prowler CLI:
|
||||
|
||||
```bash
|
||||
export HUAWEICLOUD_ACCESS_KEY_ID="your-access-key-id"
|
||||
export HUAWEICLOUD_SECRET_ACCESS_KEY="your-secret-access-key"
|
||||
|
||||
prowler huaweicloud
|
||||
```
|
||||
|
||||
Read more in the [Huawei Cloud documentation](/user-guide/providers/huaweicloud/getting-started-huaweicloud). Explore all Huawei Cloud checks at [Prowler Hub](https://hub.prowler.com/check?provider=huaweicloud).
|
||||
|
||||
### 🔍 Checks
|
||||
|
||||
#### AWS
|
||||
|
||||
- `codecommit_repository_no_secrets`, alongside the new `codecommit` service, scans files tracked at the tip of each repository's default branch for hardcoded secrets. Thanks to @Sid-0602!
|
||||
- `glue_catalog_connection_no_secrets` detects secrets in Glue Data Catalog connection properties. Thanks to @l46983284-cpu, @Rishi943, and @UTKARSH698!
|
||||
- `ec2_instance_stopped_older_than_specific_days` detects EC2 instances stopped longer than a configurable number of days (default 30). Thanks to @Nithin078!
|
||||
- `sagemaker_endpoint_config_kms_encryption_enabled` verifies SageMaker endpoint configurations use a KMS key for storage volume encryption. Thanks to @Nithin078 and @l46983284-cpu!
|
||||
|
||||
Read more in the [AWS documentation](/user-guide/providers/aws/getting-started-aws). Explore all AWS checks at [Prowler Hub](https://hub.prowler.com/check?provider=aws).
|
||||
|
||||
### 📤 OCSF Output — MITRE ATT&CK Enrichment
|
||||
|
||||
OCSF detection finding output now populates `finding_info.analytic` with the Prowler check rule and `finding_info.attacks` with MITRE ATT&CK technique and tactic objects for findings with MITRE ATT&CK compliance metadata. Thanks to @AlexanderSanin!
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- AWS Security Hub integrations now persist successful recovery checks during finding delivery, keeping connection status and the last-checked time accurate.
|
||||
- Social sign-up now creates authentication, tenant, and membership records in a single transaction, fully rolling back failed provisioning to prevent incomplete accounts.
|
||||
- The SAML configuration form keeps the ACS URL field stable while generating the callback URL and exposes the copy action only after a valid URL is available.
|
||||
- SAML users without a `userType` attribute and without an existing role now receive a least-privilege `read_only` fallback role, so role-dependent operations continue to work without granting management permissions.
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- Provider deletion, connection checks, scan creation, provider secrets, provider groups, and daily schedules now respect role provider-group visibility.
|
||||
- HTML reports escape provider-originated finding fields, preventing stored cross-site scripting through malicious cloud resource tags. https://github.com/prowler-cloud/prowler/security/advisories/GHSA-c2jg-2778-ggm4
|
||||
- Authentication with an API key whose owning user was deleted now returns `401`, and user deletion revokes the user's API keys across all their tenants.
|
||||
|
||||
### 🙌 External Contributors
|
||||
|
||||
Thank you to our community contributors for this release!
|
||||
|
||||
- @tomitobio: Huawei Cloud provider with CIS 1.0 benchmark ([#11950](https://github.com/prowler-cloud/prowler/pull/11950))
|
||||
- @paramanandmallik: four AWS privilege-escalation Attack Paths queries ([#11460](https://github.com/prowler-cloud/prowler/pull/11460))
|
||||
- @Sid-0602: AWS `codecommit` service and `codecommit_repository_no_secrets` check ([#11846](https://github.com/prowler-cloud/prowler/pull/11846))
|
||||
- @l46983284-cpu, @Rishi943, and @UTKARSH698: AWS `glue_catalog_connection_no_secrets` check ([#11963](https://github.com/prowler-cloud/prowler/pull/11963))
|
||||
- @Nithin078: AWS `ec2_instance_stopped_older_than_specific_days` ([#12076](https://github.com/prowler-cloud/prowler/pull/12076)) and `sagemaker_endpoint_config_kms_encryption_enabled` ([#12118](https://github.com/prowler-cloud/prowler/pull/12118), co-authored with @l46983284-cpu) checks
|
||||
- @AlexanderSanin: MITRE ATT&CK enrichment in OCSF detection finding output ([#11492](https://github.com/prowler-cloud/prowler/pull/11492))
|
||||
- @stefanobaldo: GCP gen2 Cloud Functions IAM policy retrieval is now thread-safe ([#12107](https://github.com/prowler-cloud/prowler/pull/12107))
|
||||
- @rayair250-droid: GCP SSH and RDP firewall checks now detect exposed ports in any position within multi-port rules ([#12115](https://github.com/prowler-cloud/prowler/pull/12115))
|
||||
- @jbchief-dev: secret ignore patterns now use Kingfisher-compatible LF line indexing ([#12141](https://github.com/prowler-cloud/prowler/pull/12141))
|
||||
- @bmbferreira: Helm chart improvements — immutable chart versions on release ([#12056](https://github.com/prowler-cloud/prowler/pull/12056)) and capped Celery worker concurrency ([#12054](https://github.com/prowler-cloud/prowler/pull/12054))
|
||||
|
||||
See the [full release notes on GitHub](https://github.com/prowler-cloud/prowler/releases/tag/5.37.0) for the complete list of changes.
|
||||
</Update>
|
||||
|
||||
<Update label="v5.36.0" description="July 24, 2026">
|
||||
### 🎫 Finding Groups - Jira
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'AI Skills System for Prowler Contributors'
|
||||
description: 'Enable AI coding assistants like Claude Code, Cursor, and Copilot to follow Prowler patterns using structured skills, setup scripts, and triggers.'
|
||||
title: 'AI Skills System'
|
||||
---
|
||||
|
||||
This guide explains the AI Skills system that provides on-demand context and patterns to AI agents working with the Prowler codebase.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Alibaba Cloud Provider Implementation'
|
||||
description: 'Explore the Alibaba Cloud provider in Prowler, covering session management, RAM authentication, STS credentials, region discovery, and service classes.'
|
||||
title: 'Alibaba Cloud Provider'
|
||||
---
|
||||
|
||||
This page details the [Alibaba Cloud](https://www.alibabacloud.com/) provider implementation in Prowler.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: "Attack Paths Queries"
|
||||
description: "Write and maintain read-only openCypher Attack Paths queries in Prowler that traverse the Cartography cloud graph to detect privilege escalation and exposure."
|
||||
---
|
||||
|
||||
This guide explains how to write and maintain Prowler Attack Paths queries: the read-only openCypher queries that traverse the Cartography-ingested cloud graph to detect privilege escalation chains, network exposure, and other graph-shaped security risks.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'AWS Provider Implementation'
|
||||
description: 'Understand the AWS provider internals in Prowler: boto3 sessions, IAM role assumption, region and Organizations discovery, and service base classes.'
|
||||
title: 'AWS Provider'
|
||||
---
|
||||
|
||||
In this page you can find all the details about [Amazon Web Services (AWS)](https://aws.amazon.com/) provider implementation in Prowler.
|
||||
@@ -40,7 +39,7 @@ The AWS provider implementation follows the general [Provider structure](/develo
|
||||
- **Key AWS Responsibilities:**
|
||||
- Receives an `AwsProvider` instance to access session, identity, and configuration.
|
||||
- Manages clients for all services by regions.
|
||||
- Provides `__threading_call__` method to make boto3 calls in parallel. By default, these calls are made by region, but it can be overridden with the first parameter of the method and use by resource.
|
||||
- Provides `__threading_call__` method to make boto3 calls in parallel. By default, this calls are made by region, but it can be overridden with the first parameter of the method and use by resource.
|
||||
- Exposes common audit context (`audited_account`, `audited_account_arn`, `audited_partition`, `audited_resources`) to subclasses.
|
||||
|
||||
### Exception Handling
|
||||
@@ -60,7 +59,7 @@ The generic service pattern is described in [service page](/developer-guide/serv
|
||||
- Directly in the code, in location [`prowler/providers/aws/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/aws/services)
|
||||
- In the [Prowler Hub](https://hub.prowler.com/). For a more human-readable view.
|
||||
|
||||
The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other services already implemented as reference. In next subsection you can find a list of common patterns that are used across all AWS services.
|
||||
The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other services already implemented as reference. In next subsection you can find a list of common patterns that are used accross all AWS services.
|
||||
|
||||
### AWS Service Common Patterns
|
||||
|
||||
@@ -69,7 +68,7 @@ The best reference to understand how to implement a new service is following the
|
||||
- The constructor (`__init__`) always calls `super().__init__` with the service name and provider (e.g. `super().__init__(__class__.__name__, provider))`). Ensure that the service name in boto3 is the same that you use in the constructor. Usually is used the `__class__.__name__` to get the service name because it is the same as the class name.
|
||||
- Resource containers **must** be initialized in the constructor. They should be dictionaries, with the key being the resource ARN or equivalent unique identifier and the value being the resource object.
|
||||
- Resource discovery and attribute collection are parallelized using `self.__threading_call__`, typically by region or resource, for performance. The first parameter of the method is the iterator, if not provided, it will be the region; but if present indicate an array of the resources to be processed.
|
||||
- Resource filtering is consistently enforced using `self.audit_resources` attribute and `is_resource_filtered` function, it is used to see if user has provided some resource that is not in the audit scope, so we can skip it in the service logic. Normally it is used before storing the resource in the service container as follows: `if not self.audit_resources or (is_resource_filtered(resource["arn"], self.audit_resources)):`.
|
||||
- Resource filtering is consistently enforced using `self.audit_resources` attribute and `is_resource_filtered` function, it is used to see if user has provided some resource that is not in the audit scope, so we can skip it in the service logic. Normally it is used befor storing the resource in the service container as follows: `if not self.audit_resources or (is_resource_filtered(resource["arn"], self.audit_resources)):`.
|
||||
- All AWS resources are represented as Pydantic `BaseModel` classes, providing type safety and structured access to resource attributes.
|
||||
- AWS API calls are wrapped in try/except blocks, with specific handling for `ClientError` and generic exceptions, always logging errors.
|
||||
- If ARN is not present for some resource, it can be constructed using string interpolation, always including partition, service, region, account, and resource ID.
|
||||
@@ -163,7 +162,7 @@ When you instantiate `Check_Report_AWS`, you must provide the check metadata and
|
||||
|
||||
If the resource object does not contain the required attributes, you must set them manually in the check logic.
|
||||
|
||||
Other attributes are inherited from the `Check_Report` class, from those you **always** have to set the `status` and `status_extended` attributes in the check logic.
|
||||
Other attributes are inherited from the `Check_Report` class, from that ones you **always** have to set the `status` and `status_extended` attributes in the check logic.
|
||||
|
||||
#### Example Usage
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Azure Provider Implementation'
|
||||
description: 'Learn how the Azure provider in Prowler manages Service Principal, CLI, browser, and Managed Identity auth, subscription discovery, and Entra ID scanning.'
|
||||
title: 'Azure Provider'
|
||||
---
|
||||
|
||||
In this page you can find all the details about [Microsoft Azure](https://azure.microsoft.com/) provider implementation in Prowler.
|
||||
@@ -58,7 +57,7 @@ The generic service pattern is described in [service page](/developer-guide/serv
|
||||
- Directly in the code, in location [`prowler/providers/azure/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/azure/services)
|
||||
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.
|
||||
|
||||
The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other services already implemented as reference. In next subsection you can find a list of common patterns that are used across all Azure services.
|
||||
The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other services already implemented as reference. In next subsection you can find a list of common patterns that are used accross all Azure services.
|
||||
|
||||
### Azure Service Common Patterns
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Check Metadata Writing Guidelines'
|
||||
description: 'Follow Prowler conventions for writing check titles, descriptions, risk, remediation, and status_extended fields when authoring check metadata JSON files.'
|
||||
title: 'Check Metadata Guidelines'
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Creating New Prowler Checks'
|
||||
description: 'Step-by-step guide to add a new security check to Prowler, covering folder structure, naming conventions, metadata files, test scenarios, and CLI validation.'
|
||||
title: 'Prowler Checks'
|
||||
---
|
||||
|
||||
This guide explains how to create new checks in Prowler.
|
||||
@@ -173,7 +172,7 @@ else:
|
||||
|
||||
### Resource Identification in Prowler
|
||||
|
||||
Each check **must** populate the report with a unique identifier for the audited resource. This identifier or identifiers are going to depend on the provider and the resource that is being audited. Here are the criteria for each provider:
|
||||
Each check **must** populate the report with an unique identifier for the audited resource. This identifier or identifiers are going to depend on the provider and the resource that is being audited. Here are the criteria for each provider:
|
||||
|
||||
- AWS
|
||||
- Amazon Resource ID — `report.resource_id`.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Configurable Checks with audit_config'
|
||||
description: 'Make Prowler checks configurable through the audit_config object and config.yaml, letting users override thresholds, defaults, and check behavior at scan time.'
|
||||
title: 'Configurable Checks in Prowler'
|
||||
---
|
||||
|
||||
Prowler empowers users to extend and adapt cloud security coverage by making checks configurable through the use of the `audit_config` object. This approach enables customization of checks to meet specific requirements through a configuration file.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Debugging Prowler with VSCode'
|
||||
description: 'Set up Visual Studio Code launch configurations and debugpy to step through Prowler checks, services, and provider code for AWS, Azure, GCP, and more.'
|
||||
title: 'Debugging in Prowler'
|
||||
---
|
||||
|
||||
Debugging in Prowler simplifies the development process, allowing developers to efficiently inspect and resolve unexpected issues during execution.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Contributing to Prowler Documentation'
|
||||
description: 'Contribute to Prowler docs built with Mintlify: learn the section structure, AI agent guidelines, local preview setup, and style conventions for MDX pages.'
|
||||
title: 'Contributing to Documentation'
|
||||
---
|
||||
|
||||
Prowler documentation is built using [Mintlify](https://www.mintlify.com/docs), allowing contributors to easily add or enhance documentation.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'End-to-End Tests for Prowler App'
|
||||
description: 'Write Playwright end-to-end tests for Prowler App covering user journeys, Page Object Models, storage state reuse, and organizing spec files by feature area.'
|
||||
title: 'End-2-End Tests for Prowler Local Server'
|
||||
---
|
||||
|
||||
End-to-end (E2E) tests validate complete user flows in Prowler Local Server (UI + API). These tests are implemented with [Playwright](https://playwright.dev/) under the `ui/tests` folder and are designed to run against a Prowler Local Server environment.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Environment Variable Naming Conventions'
|
||||
description: 'Namespace Prowler App, API, SDK, and MCP Server environment variables with component prefixes like UI_ and API_ to avoid conflicts in a shared .env file.'
|
||||
title: 'Environment Variable Naming Convention'
|
||||
---
|
||||
|
||||
Prowler is a monorepo composed of several runtime components — Prowler Local Server (the web user interface), Prowler API (the backend), Prowler SDK, and Prowler MCP Server (Model Context Protocol) — that frequently share a single `.env` file. To keep that shared configuration unambiguous, each component namespaces its environment variables with a component-specific prefix.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Google Cloud (GCP) Provider Implementation'
|
||||
description: 'Dive into the GCP provider in Prowler: Application Default Credentials, service accounts, OAuth, impersonation, and multi-project and organization discovery.'
|
||||
title: 'Google Cloud Provider'
|
||||
---
|
||||
|
||||
This page details the [Google Cloud Platform (GCP)](https://cloud.google.com/) provider implementation in Prowler.
|
||||
@@ -103,7 +102,7 @@ The generic service pattern is described in [service page](/developer-guide/serv
|
||||
- Directly in the code, in location [`prowler/providers/gcp/services/`](https://github.com/prowler-cloud/prowler/tree/master/prowler/providers/gcp/services)
|
||||
- In the [Prowler Hub](https://hub.prowler.com/) for a more human-readable view.
|
||||
|
||||
The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other services already implemented as reference. In next subsection you can find a list of common patterns that are used across all GCP services.
|
||||
The best reference to understand how to implement a new service is following the [service implementation documentation](/developer-guide/services#adding-a-new-service) and taking other services already implemented as reference. In next subsection you can find a list of common patterns that are used accross all GCP services.
|
||||
|
||||
### GCP Service Common Patterns
|
||||
|
||||
@@ -114,7 +113,7 @@ The best reference to understand how to implement a new service is following the
|
||||
- Only projects with the API enabled are included in the audit scope.
|
||||
- Resource discovery and attribute collection can be parallelized using `self.__threading_call__`, typically by region/zone or resource.
|
||||
- All GCP resources are represented as Pydantic `BaseModel` classes, providing type safety and structured access to resource attributes.
|
||||
- Each GCP API call is wrapped in try/except blocks, always logging errors.
|
||||
- Each GCP API calls are wrapped in try/except blocks, always logging errors.
|
||||
- **Retry Configuration**: All `request.execute()` calls must include `num_retries=DEFAULT_RETRY_ATTEMPTS` for automatic retry on rate limiting errors (HTTP 429).
|
||||
- Tags and additional attributes that cannot be retrieved from the default call should be collected and stored for each resource using dedicated methods and threading.
|
||||
|
||||
@@ -162,7 +161,7 @@ When you instantiate `Check_Report_GCP`, you must provide the check metadata and
|
||||
- Defaults to "global" if none are available.
|
||||
|
||||
All these attributes can be overridden by passing the corresponding argument to the constructor. If the resource object does not contain the required attributes, you must set them manually.
|
||||
Other attributes are inherited from the `Check_Report` class, from those you **always** have to set the `status` and `status_extended` attributes in the check logic.
|
||||
Others attributes are inherited from the `Check_Report` class, from that ones you **always** have to set the `status` and `status_extended` attributes in the check logic.
|
||||
|
||||
#### Example Usage
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'GitHub Provider Implementation'
|
||||
description: 'See how the GitHub provider in Prowler handles Personal Access Token, OAuth App, and GitHub App authentication to audit repositories and organizations.'
|
||||
title: 'GitHub Provider'
|
||||
---
|
||||
|
||||
This page details the [GitHub](https://github.com/) provider implementation in Prowler.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Integration Tests for Prowler'
|
||||
description: 'Placeholder page tracking upcoming guidance on Prowler integration tests, covering how to validate multi-component flows across the SDK, API, and UI.'
|
||||
title: 'Integration Tests'
|
||||
---
|
||||
|
||||
Coming soon ...
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Creating New Prowler Integrations'
|
||||
description: 'Build integrations that ship Prowler findings to Slack, Jira, AWS Security Hub, and other platforms by scripting API calls and configuring output pipelines.'
|
||||
title: 'Creating a New Integration'
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Introduction to Developing in Prowler'
|
||||
description: 'Get started as a Prowler contributor: pick a good first issue, extend checks, services, or integrations, and follow the AI-driven contribution workflow.'
|
||||
title: 'Introduction to developing in Prowler'
|
||||
---
|
||||
|
||||
Thanks for your interest in contributing to Prowler!
|
||||
@@ -76,7 +75,7 @@ Remember, our community is here to help! If you need guidance, do not hesitate t
|
||||
|
||||
|
||||
|
||||
## Setting Up Your Development Environment
|
||||
## Setting up your development environment
|
||||
|
||||
### Prerequisites
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Kubernetes Provider Implementation'
|
||||
description: 'Explore the Kubernetes provider in Prowler, including kubeconfig context loading, namespace discovery, in-cluster execution, and service class scaffolding.'
|
||||
title: 'Kubernetes Provider'
|
||||
---
|
||||
|
||||
This page details the [Kubernetes](https://kubernetes.io/) provider implementation in Prowler.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Prowler Lighthouse AI Architecture'
|
||||
description: 'Inside Prowler Lighthouse AI: a three-tier Next.js, API route, and Langchain agent design that connects LLMs to security data through MCP tool calls.'
|
||||
title: 'Lighthouse AI Architecture'
|
||||
---
|
||||
|
||||
This document describes the internal architecture of Prowler Lighthouse AI, enabling developers to understand how components interact and where to add new functionality.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'LLM Provider Implementation'
|
||||
description: 'Learn how the Prowler LLM provider uses promptfoo to run red team security tests against OpenAI and other large language models with reusable test suites.'
|
||||
title: 'LLM Provider'
|
||||
---
|
||||
|
||||
This page details the [Large Language Model (LLM)](https://en.wikipedia.org/wiki/Large_language_model) provider implementation in Prowler.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Microsoft 365 (M365) Provider Implementation'
|
||||
description: 'Understand the Microsoft 365 provider in Prowler, PowerShell 7.4+ requirements, Exchange Online and Teams modules, and Entra ID tenant scanning.'
|
||||
title: 'Microsoft 365 (M365) Provider'
|
||||
---
|
||||
|
||||
This page details the [Microsoft 365 (M365)](https://www.microsoft.com/en-us/microsoft-365) provider implementation in Prowler.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: 'Extending the Prowler MCP Server'
|
||||
description: 'Add new tools and sub-servers to the Prowler MCP Server so AI assistants like Claude Desktop and Cursor can query Prowler App, Hub, and documentation content.'
|
||||
title: 'Extending the MCP Server'
|
||||
---
|
||||
|
||||
This guide explains how to extend the Prowler MCP Server with new tools and features.
|
||||
@@ -353,7 +352,7 @@ result = await self.api_client.poll_task_until_complete(
|
||||
|
||||
### Tool Docstrings
|
||||
|
||||
Tool docstrings become the description that is going to be read by the LLM. Provide clear usage instructions and common workflows:
|
||||
Tool docstrings become description that is going to be read by the LLM. Provide clear usage instructions and common workflows:
|
||||
|
||||
```python
|
||||
async def search_items(self, status: str = Field(...)) -> dict:
|
||||
@@ -427,150 +426,6 @@ For complete installation and deployment options, see:
|
||||
|
||||
For development I recommend to use the [Model Context Protocol Inspector](https://github.com/modelcontextprotocol/inspector) as MCP client to test and debug your tools.
|
||||
|
||||
## Testing
|
||||
|
||||
Tests live in `mcp_server/tests/`, mirroring the source tree, and use the `test_*.py`
|
||||
prefix (the same convention as the API, not the SDK's `*_test.py` suffix).
|
||||
|
||||
From `mcp_server/`:
|
||||
|
||||
```bash
|
||||
cd mcp_server
|
||||
|
||||
uv run pytest # Whole suite
|
||||
uv run pytest tests/prowler_app/models # One area
|
||||
uv run pytest --cov=./prowler_mcp_server # With coverage
|
||||
```
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
make test-mcp # Runs the MCP suite exactly as CI does
|
||||
```
|
||||
|
||||
Async tests need no marker — `asyncio_mode` is set to `auto`.
|
||||
|
||||
### Reading the Coverage Numbers
|
||||
|
||||
<Warning>
|
||||
Coverage here has a high floor that means nothing. `coverage.py` measures
|
||||
*statements*, and in a Pydantic model module nearly every statement is a class-body
|
||||
field declaration that runs at **import** time. `prowler_app/server.py` imports
|
||||
every tool module — and therefore every model module — when it is first imported,
|
||||
so all of those declarations execute and count as covered before a single test runs.
|
||||
|
||||
Importing the package and executing no tests at all already reports **36% overall**,
|
||||
with individual model modules between 54% and 84%. A model module sitting at ~68%
|
||||
with no tests written for it has **none** of its behaviour covered: the covered lines
|
||||
are its imports, `class` statements and `Field(...)` declarations, and the missing
|
||||
ranges are its `from_api_response()` bodies.
|
||||
|
||||
Judge a module against that import-only floor, not against zero, and do not set a
|
||||
Codecov target from the raw total.
|
||||
</Warning>
|
||||
|
||||
### Shared Fixtures
|
||||
|
||||
All fixtures live in `mcp_server/tests/conftest.py`. Three are autouse and apply to
|
||||
every test: the environment is pinned to deterministic values, real socket
|
||||
connections are blocked, and the API client singleton registry is snapshotted and
|
||||
restored.
|
||||
|
||||
| Fixture | What it gives you |
|
||||
|---------|-------------------|
|
||||
| `mock_api_client` | The API client singleton with its transport mocked. The workhorse. |
|
||||
| `mock_router` | Route registry and request recorder |
|
||||
| `mcp_root_server` | The mounted root server, for in-memory client tests |
|
||||
| `health_client` | Starlette `TestClient` for the `/health` route |
|
||||
| `http_request_headers` | Injects request headers for HTTP-transport auth tests |
|
||||
| `hub_router` / `docs_router` | Mock the Hub and Docs sub-servers' sync HTTP clients |
|
||||
| `api_client` / `isolated_api_client` | The live singleton / a freshly-constructed one |
|
||||
|
||||
Helpers live in `mcp_server/tests/helpers/`: JSON:API document builders
|
||||
(`jsonapi.py`), the `MockRouter` (`http.py`), tool-contract assertions
|
||||
(`assertions.py`) and fake credentials (`tokens.py`).
|
||||
|
||||
### Writing a Tool Test
|
||||
|
||||
Drive tools through an in-memory MCP client, and open the client inside the test —
|
||||
FastMCP warns that holding a client in a fixture causes event-loop problems.
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
from tests.helpers.jsonapi import jsonapi_collection, jsonapi_resource
|
||||
|
||||
FINDING_ATTRIBUTES = {
|
||||
"uid": "prowler-aws-s3_bucket_public_access-123456789012-us-east-1-my-bucket",
|
||||
"status": "FAIL",
|
||||
"severity": "high",
|
||||
"status_extended": "S3 bucket my-bucket is publicly accessible.",
|
||||
"delta": "new",
|
||||
"muted": False,
|
||||
"muted_reason": None,
|
||||
"check_metadata": {"checkid": "s3_bucket_public_access"},
|
||||
}
|
||||
|
||||
|
||||
async def test_search_without_dates_queries_the_latest_scan_endpoint(
|
||||
mcp_root_server, mock_api_client, mock_router
|
||||
):
|
||||
"""With no date range the tool targets the cheaper `/findings/latest`."""
|
||||
mock_router.add(
|
||||
"GET",
|
||||
"/api/v1/findings/latest",
|
||||
json=jsonapi_collection(
|
||||
[jsonapi_resource("findings", "f1", FINDING_ATTRIBUTES)]
|
||||
),
|
||||
)
|
||||
|
||||
async with Client(mcp_root_server) as client:
|
||||
result = await client.call_tool("prowler_search_security_findings", {})
|
||||
|
||||
assert result.data["findings"][0]["check_id"] == "s3_bucket_public_access"
|
||||
assert mock_router.paths() == ["GET /api/v1/findings/latest"]
|
||||
```
|
||||
|
||||
The exemplar suite covers `findings` end to end — `tests/prowler_app/models/test_findings.py`
|
||||
and `tests/prowler_app/tools/test_findings.py`. It is deliberately one feature
|
||||
across both layers rather than a scattering of unrelated samples, and `findings`
|
||||
is the feature that exercises the whole foundation: two-tier models, nested
|
||||
sub-models, both relationship shapes, endpoint switching on a date range,
|
||||
list-to-CSV filter encoding, and a tool that returns prose instead of a model.
|
||||
|
||||
Note the two files share a name. That is why `__init__.py` is required in every
|
||||
`tests/` subdirectory here — without it they would collide on import.
|
||||
|
||||
<Warning>
|
||||
Tool parameters are declared with pydantic `Field(default=...)`, and only FastMCP's
|
||||
tool wrapper resolves those defaults. Calling a tool method directly with an
|
||||
argument omitted leaves it as a raw `FieldInfo` object, which is truthy — so a
|
||||
filter such as `if email:` silently builds a query out of the `FieldInfo` repr.
|
||||
Call tools through the client, or pass every argument explicitly.
|
||||
</Warning>
|
||||
|
||||
### Why the API Key Is Pinned, Not Stripped
|
||||
|
||||
`prowler_app/server.py` builds every tool at import time. Constructing a tool
|
||||
reaches `ProwlerAppAuth`, which raises when `PROWLER_API_KEY` is missing, and
|
||||
`load_all_tools` swallows that error per tool class. The result is that the whole
|
||||
`prowler_*` namespace registers **zero** tools while the server still logs
|
||||
"Successfully mounted Prowler tools server".
|
||||
|
||||
The suite therefore pins a fake key in `[tool.pytest_env]`, which is applied before
|
||||
any test module is imported, and `tests/test_server.py` asserts each namespace is
|
||||
non-empty so this failure can never return silently.
|
||||
|
||||
<Note>
|
||||
`ProwlerAppAuth` resolves `PROWLER_MCP_TRANSPORT_MODE` and `API_BASE_URL` in its
|
||||
default arguments, which Python evaluates once at module import. `monkeypatch.setenv`
|
||||
cannot change them — pass `mode=` and `base_url=` explicitly in auth tests.
|
||||
</Note>
|
||||
|
||||
For the full set of rules and templates, see the
|
||||
[`prowler-test-mcp` skill](https://github.com/prowler-cloud/prowler/blob/master/skills/prowler-test-mcp/SKILL.md)
|
||||
and the [official FastMCP testing guide](https://gofastmcp.com/development/tests).
|
||||
|
||||
## Related Documentation
|
||||
|
||||
<CardGroup cols={2}>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user