Repository navigation
ci: parity cleanup with the engine baseline #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bc6e282
39944ea
14d77ce
0f038ac
e7ebbca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||
| name: DockSec | ||||||
|
|
||||||
| on: | ||||||
| push: | ||||||
| branches: [master] | ||||||
| paths: | ||||||
| - 'Dockerfile' | ||||||
| - '.github/workflows/docksec.yml' | ||||||
| pull_request: | ||||||
| paths: | ||||||
| - 'Dockerfile' | ||||||
| - '.github/workflows/docksec.yml' | ||||||
| schedule: | ||||||
| - cron: '0 6 * * 1' | ||||||
|
|
||||||
| permissions: | ||||||
| contents: read | ||||||
|
|
||||||
| jobs: | ||||||
| scan: | ||||||
| name: Scan Dockerfile | ||||||
| runs-on: ubuntu-latest | ||||||
| timeout-minutes: 15 | ||||||
| permissions: | ||||||
| contents: read | ||||||
| security-events: write | ||||||
| steps: | ||||||
| - name: Checkout | ||||||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||||||
| with: | ||||||
| persist-credentials: false | ||||||
|
|
||||||
| - name: Run DockSec | ||||||
| uses: OWASP/DockSec@d52c8e928552dc39003abda21ac6bc4662b06c39 # v2026.9.21 | ||||||
| with: | ||||||
| dockerfile: Dockerfile | ||||||
| scan_only: 'true' | ||||||
| sarif: 'true' | ||||||
| output_dir: ${{ github.workspace }}/docksec-results | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Write SARIF to the container-mounted workspace.
Proposed fix- output_dir: ${{ github.workspace }}/docksec-results
+ output_dir: /github/workspace/docksec-results📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
| - name: Upload SARIF to Code Scanning | ||||||
| # Not `if: always()`: on a fork pull_request, GITHUB_TOKEN is read-only and this | ||||||
| # upload would fail every time, turning a routine fork contribution into a red | ||||||
| # required check. Skip it there instead of forcing a failure. | ||||||
| if: always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) | ||||||
| uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v3 | ||||||
| with: | ||||||
| sarif_file: docksec-results | ||||||
| category: docksec | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| name: Issue link | ||
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, edited, reopened, synchronize, labeled, unlabeled] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Recheck the issue when its state changes. If issue 🧰 Tools🪛 zizmor (1.30.0)[warning] 3-5: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting (concurrency-limits) 🤖 Prompt for AI Agents |
||
|
|
||
| permissions: | ||
| issues: read | ||
| pull-requests: read | ||
|
|
||
| jobs: | ||
| issue-link: | ||
| name: issue-link | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/github-script@v9 | ||
| with: | ||
| script: | | ||
| const pr = context.payload.pull_request; | ||
| if (pr.user.type === 'Bot') return core.info(`bot author ${pr.user.login}, exempt`); | ||
| if (pr.labels.some((label) => label.name === 'no-issue')) return core.info('no-issue label, exempt'); | ||
| const pattern = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?|delivers?(?:\s+issue)?)\b:?\s*(?:https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/issues\/|#)(\d+)/gi; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🛡️ Detected with Advanced Tier | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: sed -n '1,110p' .github/workflows/issue-link.yml
rg -n 'issue.link|open issue|no-issue|closing keyword|Closes #' .github README* CONTRIBUTING* 2>/dev/nullRepository: rennf93/flaskapi-guard Length of output: 2588 🏁 Script executed: set -e
printf '%s\n' '--- changed workflow diff ---'
git diff --unified=40 4ca35428e0411459b19bc8d3f0a648e1f5a431ed bc6e282263484575366f915456e2a63aa73c9f30 -- .github/workflows/issue-link.yml
printf '%s\n' '--- repository policy references ---'
rg -n -i --glob '!*.lock' --glob '!*.min.*' 'issue|closing keyword|closes|fixes|resolves|no-issue|pull request' .github README* CONTRIBUTING* 2>/dev/null || true
printf '%s\n' '--- base workflow ---'
git show 4ca35428e0411459b19bc8d3f0a648e1f5a431ed:.github/workflows/issue-link.yml 2>/dev/null || trueRepository: rennf93/flaskapi-guard Length of output: 10615 🌐 Web query:
💡 Result: Validate the repository in full issue URLs. GitHub supports cross-repository closing references, but this workflow requires an issue in the local repository. The pattern accepts a URL from any repository and extracts only its number. A matching open local issue can therefore satisfy the check. Compare the URL repository with Validate URL ownership- const pattern = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?|delivers?(?:\s+issue)?)\b:?\s*(?:https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/issues\/|#)(\d+)/gi;
- const numbers = [...new Set([...(pr.body || '').matchAll(pattern)].map((match) => Number(match[1])))];
+ const pattern = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?|delivers?(?:\s+issue)?)\b:?\s*(?:(?:https:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/issues\/)|#)(\d+)/gi;
+ const repository = `${context.repo.owner}/${context.repo.repo}`.toLowerCase();
+ const numbers = [...new Set([...(pr.body || '').matchAll(pattern)]
+ .filter((match) => !match[1] || `${match[1]}/${match[2]}`.toLowerCase() === repository)
+ .map((match) => Number(match[3])))];🤖 Prompt for AI Agents |
||
| const numbers = [...new Set([...(pr.body || '').matchAll(pattern)].map((match) => Number(match[1])))]; | ||
| if (numbers.length === 0) return core.setFailed('PR body must say "Closes #N" for an open issue in this repo, or the PR must carry the no-issue label'); | ||
| const reasons = []; | ||
| for (const number of numbers) { | ||
| try { | ||
| const { data } = await github.rest.issues.get({ ...context.repo, issue_number: number }); | ||
| if (data.pull_request) reasons.push(`#${number} is a pull request, not an issue`); | ||
| else if (data.state !== 'open') reasons.push(`#${number} is ${data.state}`); | ||
| else return core.info(`delivers open issue #${number}`); | ||
| } catch (error) { | ||
| reasons.push(`#${number}: ${error.message}`); | ||
| } | ||
| } | ||
| core.setFailed(reasons.join('; ')); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| name: Live Smoke | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - master | ||
| paths: | ||
| - 'flaskapi_guard/**' | ||
| - 'examples/**' | ||
| - '.github/workflows/live-smoke.yml' | ||
| pull_request: | ||
| paths: | ||
| - 'flaskapi_guard/**' | ||
| - 'examples/**' | ||
| - '.github/workflows/live-smoke.yml' | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: live-smoke-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| live-smoke: | ||
| name: Dockerized smoke run (simple_app) | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
| env: | ||
| LIVE_SMOKE_PROJECT: flaskapi-guard-live-smoke | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v7 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@v7 | ||
| with: | ||
| python-version: '3.12' | ||
|
|
||
| - name: Install uv | ||
| run: | | ||
| pip install uv | ||
|
|
||
| - name: Build the flaskapi_guard wheel from this checkout | ||
| run: | | ||
| uv build --wheel --out-dir examples/live_smoke_wheels | ||
|
|
||
| - name: Stage the example app with the checkout wheel | ||
| run: | | ||
| rm -rf live_smoke_stack | ||
| cp -r examples/simple_app live_smoke_stack | ||
| wheel=$(ls examples/live_smoke_wheels/flaskapi_guard-*.whl | head -n 1) | ||
| cp "$wheel" live_smoke_stack/ | ||
| python3 - <<'EOF' | ||
| import pathlib | ||
| stack = pathlib.Path("live_smoke_stack") | ||
| wheel = next(stack.glob("flaskapi_guard-*.whl")) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| req = stack / "requirements.txt" | ||
| lines = [line for line in req.read_text().splitlines() if not line.startswith("flaskapi_guard")] | ||
| lines.append(f"flaskapi_guard @ file:///app/{wheel.name}") | ||
| req.write_text("\n".join(lines) + "\n") | ||
| dockerfile = stack / "Dockerfile" | ||
| df = dockerfile.read_text() | ||
| anchor = "COPY requirements.txt /app/" | ||
| assert anchor in df | ||
| dockerfile.write_text(df.replace(anchor, anchor + f"\nCOPY {wheel.name} /app/")) | ||
| EOF | ||
|
|
||
| - name: Bring the smoke stack up | ||
| run: | | ||
| docker compose -p "$LIVE_SMOKE_PROJECT" -f live_smoke_stack/docker-compose.yml up --build -d --wait | ||
|
|
||
| - name: Run smoke assertions | ||
| run: | | ||
| set -euo pipefail | ||
| base="http://localhost:8000" | ||
|
|
||
| # Wait for the app to finish initializing (engine redis + ipinfo setup) | ||
| ready="" | ||
| for i in $(seq 1 30); do | ||
| if [ "$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 "$base/")" = "200" ]; then | ||
| ready=1; break | ||
| fi | ||
| sleep 2 | ||
| done | ||
| if [ -z "$ready" ]; then | ||
| echo "FAIL: app never became ready"; exit 1 | ||
| fi | ||
|
|
||
| check() { | ||
| local name="$1" expected="$2"; shift 2 | ||
| local status | ||
| status=$(curl -sS --max-time 10 -o /tmp/smoke_body -w '%{http_code}' "$@") || { | ||
| echo "FAIL: $name (curl error)"; exit 1 | ||
| } | ||
| if [ "$status" != "$expected" ]; then | ||
| echo "FAIL: $name (expected $expected, got $status)"; cat /tmp/smoke_body; exit 1 | ||
| fi | ||
| echo "ok: $name -> $status" | ||
| } | ||
|
|
||
| check "GET / serves" 200 "$base/" | ||
| check "GET /health serves" 200 "$base/health" | ||
|
|
||
| - name: Dump container logs on failure | ||
| if: failure() | ||
| run: | | ||
| docker compose -p "$LIVE_SMOKE_PROJECT" -f live_smoke_stack/docker-compose.yml logs --no-color | ||
|
|
||
| - name: Bring the smoke stack down | ||
| if: always() | ||
| run: | | ||
| docker compose -p "$LIVE_SMOKE_PROJECT" -f live_smoke_stack/docker-compose.yml down -v --remove-orphans | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,64 @@ | ||||||
| name: Upstream Drift | ||||||
|
|
||||||
| on: | ||||||
| schedule: | ||||||
| - cron: '0 3 * * *' | ||||||
| workflow_dispatch: | ||||||
|
|
||||||
| jobs: | ||||||
| drift: | ||||||
| name: Test against guard-core@master | ||||||
| runs-on: ubuntu-latest | ||||||
|
|
||||||
| services: | ||||||
| redis: | ||||||
| image: redis | ||||||
| ports: | ||||||
| - 6379:6379 | ||||||
| options: >- | ||||||
| --health-cmd "redis-cli ping" | ||||||
| --health-interval 10s | ||||||
| --health-timeout 5s | ||||||
| --health-retries 5 | ||||||
|
|
||||||
| steps: | ||||||
| - name: Checkout flaskapi-guard | ||||||
| uses: actions/checkout@v7 | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🛡️ Detected with Advanced Tier | 🔵 Trivial | ⚖️ Poor tradeoff 🧩 Analysis chain🏁 Script executed: grep -rn 'uses:' .github/workflows/
ls -a .github; cat .github/zizmor.yml 2>/dev/null; rg -n -i 'pin|sha' CONTRIBUTING* .github 2>/dev/null | head -50Repository: rennf93/flaskapi-guard Length of output: 4850 Security Misconfiguration Reachability: Internal Treat SHA pinning as an optional repository-wide hardening refactor. Multiple workflows use 🧰 Tools🪛 zizmor (1.30.0)[warning] 1-65: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block (excessive-permissions) [warning] 9-65: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block (excessive-permissions) [error] 26-26: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI Agents |
||||||
| with: | ||||||
| persist-credentials: false | ||||||
|
|
||||||
| - name: Checkout guard-core@master | ||||||
| uses: actions/checkout@v7 | ||||||
| with: | ||||||
| repository: rennf93/guard-core | ||||||
| ref: master | ||||||
| path: guard-core-upstream | ||||||
| persist-credentials: false | ||||||
|
|
||||||
| - name: Set up Python | ||||||
| uses: actions/setup-python@v7 | ||||||
| with: | ||||||
| python-version: '3.12' | ||||||
|
|
||||||
| - name: Install uv | ||||||
| run: | | ||||||
| pip install uv | ||||||
|
|
||||||
| - name: Build the guard-core wheel from guard-core@master | ||||||
| run: | | ||||||
| cd guard-core-upstream | ||||||
| pip install build | ||||||
| python -m build --wheel --outdir ../wheels | ||||||
|
|
||||||
| - name: Install the flaskapi-guard dev environment | ||||||
| run: | | ||||||
| uv sync --extra dev | ||||||
|
|
||||||
| - name: Force-install the guard-core@master wheel over the resolved one | ||||||
| run: | | ||||||
| wheel=$(ls wheels/guard_core-*.whl | head -n 1) | ||||||
| uv pip install --python .venv/bin/python --no-deps --force-reinstall "$wheel" | ||||||
|
|
||||||
| - name: Run the flaskapi-guard test suite against guard-core@master | ||||||
| run: | | ||||||
| IPINFO_TOKEN='test_token' REDIS_URL='redis://localhost:6379' REDIS_PREFIX='drift:flaskapi_guard:' uv run python -m pytest -v --cov=flaskapi_guard | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Run pytest without syncing the environment.
Proposed change- IPINFO_TOKEN='test_token' REDIS_URL='redis://localhost:6379' REDIS_PREFIX='drift:flaskapi_guard:' uv run python -m pytest -v --cov=flaskapi_guard
+ IPINFO_TOKEN='test_token' REDIS_URL='redis://localhost:6379' REDIS_PREFIX='drift:flaskapi_guard:' uv run --no-sync python -m pytest -v --cov=flaskapi_guard📝 Committable suggestion
Suggested change
🧰 Tools🪛 zizmor (1.30.0)[warning] 1-65: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block (excessive-permissions) [warning] 9-65: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block (excessive-permissions) 🤖 Prompt for AI Agents |
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |||||
| from flaskapi_guard import ( | ||||||
| BehaviorRule, | ||||||
| FlaskAPIGuard, | ||||||
| FlaskGuardResponse, | ||||||
| SecurityConfig, | ||||||
| SecurityDecorator, | ||||||
| cloud_handler, | ||||||
|
|
@@ -29,13 +30,19 @@ | |||||
|
|
||||||
|
|
||||||
| def custom_request_check(req): | ||||||
| if req.args.get("debug") == "true": | ||||||
| client_host = req.remote_addr or "unknown" | ||||||
| # The check receives the guard's abstract request protocol: | ||||||
| # query params live on req.query_params, not Flask's request.args. | ||||||
| if req.query_params.get("debug") == "true": | ||||||
| client_host = req.client_host or "unknown" | ||||||
| logger.warning(f"Blocked debug request from {client_host}") | ||||||
| return Response( | ||||||
| response='{"detail": "Debug mode not allowed"}', | ||||||
| status=403, | ||||||
| content_type="application/json", | ||||||
| # Custom checks must return the guard response type, not a raw | ||||||
| # Flask Response: the pipeline unwraps .body/.status_code itself. | ||||||
| return FlaskGuardResponse( | ||||||
| Response( | ||||||
| response='{"detail": "Debug mode not allowed"}', | ||||||
| status=403, | ||||||
| content_type="application/json", | ||||||
| ) | ||||||
| ) | ||||||
| return None | ||||||
|
|
||||||
|
|
@@ -49,7 +56,8 @@ def custom_response_modifier(resp): | |||||
|
|
||||||
|
|
||||||
| security_config = SecurityConfig( | ||||||
| whitelist=["127.0.0.1", "::1", "10.0.0.0/8"], | ||||||
| # Include the Docker client networks so host curls are not flagged | ||||||
| whitelist=["127.0.0.1", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"], | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🛡️ Detected with Advanced Tier | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: set -eu
printf '%s\n' '--- changed hunk ---'
git diff --unified=20 4ca35428e0411459b19bc8d3f0a648e1f5a431ed 14d77ceb82579ecf4ada3567095b2ca5afabf187 -- examples/simple_app/main.py
printf '%s\n' '--- guard-core references ---'
rg -n -i --hidden --glob '!*.lock' --glob '!dist/**' --glob '!build/**' 'guard[-_]core|whitelist|blacklist|allowed|blocked' .
printf '%s\n' '--- dependency manifests ---'
git ls-files | rg '(^|/)(pyproject\.toml|setup\.py|setup\.cfg|requirements[^/]*|Pipfile|poetry\.lock|uv\.lock|.*\.lock)$' | xargs -r -n1 sh -c 'echo "--- $0"; sed -n "1,220p" "$0"'Repository: rennf93/flaskapi-guard Length of output: 41153 🏁 Script executed: set -eu
printf '%s\n' '--- documented overlap contract ---'
sed -n '45,65p' docs/tutorial/decorators/access-control.md
sed -n '380,398p' docs/tutorial/decorators/access-control.md
printf '%s\n' '--- local overlap-related tests/docs ---'
rg -n -C 5 'overlap|except this|both.*whitelist|whitelist.*blacklist|blacklist.*whitelist' tests docs examplesRepository: rennf93/flaskapi-guard Length of output: 25685 Security Misconfiguration Reachability: External Remove the overlapping whitelist entry.
Remove the overlapping range- whitelist=["127.0.0.1", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"],
+ whitelist=["127.0.0.1", "::1", "10.0.0.0/8", "172.16.0.0/12"],📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| blacklist=["192.168.100.0/24"], | ||||||
| trusted_proxies=["127.0.0.1", "10.0.0.0/8"], | ||||||
| trusted_proxy_depth=2, | ||||||
|
|
@@ -63,6 +71,7 @@ def custom_response_modifier(resp): | |||||
| auto_ban_threshold=5, | ||||||
| auto_ban_duration=300, | ||||||
| enable_penetration_detection=True, | ||||||
| behavior_scan_response_body=True, | ||||||
| enable_redis=True, | ||||||
| redis_url=os.environ.get("REDIS_URL", "redis://localhost:6379"), | ||||||
| redis_prefix=os.environ.get("REDIS_PREFIX", "flaskapi_guard:"), | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Keep this CI-only change within the repository boundary.
This line changes CI test setup only. It does not update the Flask adapter, re-export guard-core API, or change Flask-specific extension orchestration. Revert it unless an approved scope exception applies.
As per coding guidelines, “This repo should only change when” the Flask adapter needs updates, guard-core exports need re-exporting from
flaskapi_guard/__init__.py, or Flask-specific extension orchestration changes.🧰 Tools
🪛 zizmor (1.30.0)
[warning] 1-82: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block
(excessive-permissions)
[warning] 81-81: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 Prompt for AI Agents
Source: Coding guidelines