feat(core): v0.4.0 - enterprise rules, OWASP migration, scan hardening, AI foundations, and signed releases - #348
Open
Vishnu2707 wants to merge 192 commits into
Open
Vishnu2707 wants to merge 192 commits into
Vishnu2707 wants to merge 192 commits into
Conversation
… compliance mappings, docs
* feat: add sentinel/ingest.py — Log Analytics ingestion via HMAC-SHA256 * feat: add sentinel/__init__.py * feat: add KQL rule — HIGH severity finding detected * feat: add KQL rule — misconfiguration wave detection * feat: add KQL rule — new resource type critical detection * Delete sentinel/rules directory * Create rules * Delete sentinel/rules * Add KQL rule for high severity findings * Add Misconfiguration Wave detection rule * Add KQL rule for persistent misconfiguration detection * Add KQL rule for new critical resource types This rule identifies new resource types with critical findings that have occurred in the last 24 hours, excluding known types from the last 30 days. * Add script to generate test findings in JSON format This script generates test findings related to security compliance and saves them in a JSON file. * Add Sentinel integration test plan and results Added a comprehensive test plan for Sentinel integration, detailing test objectives, results, and acceptance criteria for various KQL rules and data ingestion. * docs: add sentinel integration setup guide Added a comprehensive setup guide for integrating Sentinel with Azure, covering prerequisites, workspace creation, activation, environment variable setup, ingestion, log verification, KQL rules deployment, and incident verification.
* Add az_net_003.py to check NSG rules for port 443 This script detects Network Security Groups (NSGs) with unrestricted inbound access on port 443 and provides remediation guidance. * Add AZ-NET-004 rule for empty NSG detection This script detects Network Security Groups (NSGs) that have no custom security rules configured, providing details for remediation. * Add AZ-NET-005 rule for DDoS protection check This script detects virtual networks in Azure that do not have DDoS protection enabled and provides remediation steps. * feat: add rule AZ-NET-006 — public IP unassociated with any resource This rule detects public IP addresses that are not associated with any resource, providing details for remediation. * feat: add rule AZ-NET-007 — Application Gateway without WAF enabled This rule detects Application Gateways that do not have WAF enabled, logging findings and providing remediation steps. * feat: add rule AZ-NET-008 — load balancer with no backend pool This rule detects load balancers in Azure that are not configured with a backend pool, indicating potential misconfiguration or unnecessary costs. * feat: add rule AZ-NET-009 — VPN gateway using outdated IKE version This script detects VPN gateways using the outdated IKEv1 protocol and provides remediation steps to migrate to IKEv2. * feat: add rule AZ-NET-010 — subnet with no NSG attached This script detects subnets in Azure that do not have a Network Security Group (NSG) attached, logging findings and providing remediation guidance. * feat: add playbook fix_az_net_003.sh This script updates the NSG rule to restrict inbound traffic on port 443 to a specified IP range. * feat: add playbook fix_az_net_004.sh This script adds a default deny-all inbound rule to a specified NSG. * feat: add playbook fix_az_net_005.sh This script enables DDoS protection on a specified virtual network in Azure. It checks for required parameters and provides usage instructions if they are missing. * feat: add playbook fix_az_net_006.sh This script deletes unassociated public IP addresses in Azure. * feat: add playbook fix_az_net_007.sh This script enables WAF on an Application Gateway, ensuring compliance with the AZ-NET-007 rule. * feat: add playbook fix_az_net_008.sh Script to remediate AZ-NET-008 by deleting empty load balancers. * feat:add script to update VPN connection to IKEv2 This script updates a VPN connection to use IKEv2, ensuring compliance with the AZ-NET-009 rule. * feat: add playbook fix_az_net_010.sh This script attaches a specified network security group to a given subnet in a virtual network, ensuring compliance with the AZ-NET-010 rule. * Clarify description and add note for public-facing services Updated the description to clarify the risk of exposing port 443 and added a note regarding public-facing services. * Change severity level from MEDIUM to HIGH * fix: AZ-NET-005 severity changed to LOW — DDoS Standard high cost on small subscriptions * Add note about NetworkManagementClient usage Added a note regarding the creation of NetworkManagementClient directly and suggested a follow-up for consistency. * Add note about NetworkManagementClient usage Added a note regarding the use of NetworkManagementClient and suggested a follow-up for consistency. * Add additional security controls to CIS Azure benchmark * Refine control descriptions in nist_csf.json Updated descriptions for various controls to enhance clarity and specificity regarding remote access management, data protection, and security measures. * fix: add AZ-NET-003 to AZ-NET-010 to ISO27001 compliance framework Updated descriptions for various controls to clarify compliance requirements and improve security guidance. --------- Co-authored-by: Vishnu Ajith <86302373+Vishnu2707@users.noreply.github.com>
* feat: add rule AZ-STOR-003 storage lifecycle policy check * feat: add rule AZ-STOR-003 storage lifecycle policy check
* docs: add SOC 2 Type II compliance framework mapping for all 20 rules Added SOC 2 Type II framework with detailed controls for security measures and compliance requirements. * feat: add soc2 to FRAMEWORK_FILE_MAP in finding.py add soc2.json to FRAMEWORK_FILE_MAP in finding.py * feat: add soc2 to SUPPORTED_FRAMEWORKS in compliance.py Added 'soc2' to the list of supported compliance frameworks. * Add SOC 2 controls for data protection and management
* refactor: add get_virtual_networks() and get_public_ip_addresses() to AzureClient * Refactor DDoS protection check to use azure_client * refactor: AZ-NET-006 now uses azure_client.get_public_ip_addresses()
- Python syntax check on all rule files - Rule structure validation (RULE_ID, SEVERITY, FRAMEWORKS) + RULE_ID uniqueness - Hardcoded credential scan - Playbook existence + bash syntax check for every rule - Compliance JSON validation for all four framework files (inc. soc2.json) - API syntax check - Compliance vs rule cross-reference check - CI summary step with per-check pass/fail table (if: always) - Fix duplicate DESCRIPTION assignment in az_net_003.py - Add pyyaml to requirements.txt for local YAML validation - Add docs/ci-pipeline.md with local run commands and design rationale - Update CI_PIPELINE_GUIDE.md with final PR description Closes #30
* docs: update learn page statistics and add CI/CD workflow
Signed-off-by: Parth Rohit <parthrohit60@gmail.com>
* fix: push to branch explicitly, regenerate stats from current dev
update-learn-page.yml: actions/checkout leaves the runner in detached
HEAD, so the final `git push` had no upstream and would fail with
"You are not currently on a branch". Push HEAD to the triggering ref
by name instead.
Rebased onto dev and re-ran update_learn_page.py against the current
rule set rather than hand-editing numbers that would be stale again
immediately. The README feature-table regex still had the old
rule-category wording from before this branch was cut, so it failed
loud as designed instead of silently no-op'ing; updated it to match
the current sentence.
Removed the "known cleanup item" note about KeyVault vs Key Vault
naming now that the category chart renders the declared category
string directly, and corrected the Known Gaps bullet that still said
README.md counts were updated by hand, now that render_readme() covers
both files.
Signed-off-by: Parth Rohit <parthrohit60@gmail.com>
* fix: avoid inline workflow-context interpolation in push step
github-advanced-security flagged interpolating ${{ github.ref_name }}
directly into the Commit and push run: script as a potential
script-injection pattern. Pass it through an env var instead and
reference it as a shell variable, which keeps the same behavior
(push HEAD to the triggering ref) without templating untrusted
workflow context straight into a shell command.
Signed-off-by: Parth Rohit <parthrohit60@gmail.com>
* fix(docs): warn about excluded severity counts
Signed-off-by: PARTH J ROHIT <parthrohit60@gmail.com>
* style(docs): format learn page updater
Signed-off-by: PARTH J ROHIT <parthrohit60@gmail.com>
* fix(docs): count playbooks by rule match, not raw file glob
count_playbooks() globbed every *.sh in playbooks/cli/, which also picked
up review_enterprise_resilience.sh -- a shared helper several fix_*.sh
wrappers exec into, not a playbook any single rule owns. That inflated
the count past rule_count (95 rules / 96 playbooks) and broke the "every
rule ships with a matching playbook" claim in the README.
find_matching_playbooks() now mirrors the fix_<rule filename stem>.sh
convention ci.yml's playbook_check step already enforces, derives the
count from actual rule-to-playbook matches, and fails loudly if a rule
has no matching playbook or the two counts disagree. Unmatched files
(shared helpers) are reported as a non-fatal warning instead of silently
inflating the total.
Also: collect_rule_stats() now skips files with no parseable RULE_ID,
matching count_rules()'s own filter, so the severity-box totals can no
longer disagree with the headline rule count; and render_category_rows()
HTML-escapes the category name before embedding it.
Regenerated docs/learn/index.html and README.md: 95 rules / 95 playbooks.
Signed-off-by: PARTH J ROHIT <parthrohit60@gmail.com>
---------
Signed-off-by: Parth Rohit <parthrohit60@gmail.com>
Signed-off-by: PARTH J ROHIT <parthrohit60@gmail.com>
* Add enterprise network perimeter controls Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * Fix DNS evidence and WAF logging checks Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * Fix firewall threat intel and WAF rate checks Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * Preserve unknown perimeter evidence states Signed-off-by: ritiksah141 <ritiksah141@gmail.com> --------- Signed-off-by: ritiksah141 <ritiksah141@gmail.com>
…320) Partially addresses #294 - the immediate, in-repo containment slice. Not a fix for the full issue: durable multi-tenant authorization (Entra/OIDC Authorization Code + PKCE, JWKS validation, per-tenant schema/RLS) is a separate, larger body of work that needs an actual identity-provider app registration against this deployment's real domains, which is outside what a PR to this repository can provision or decide alone. That part stays open and un-claimed here. What this closes, confirmed live before this commit (verify_jwt() in api/app.py only checked the signature and expiry-if-present; g.user was never read by any route; the baseline schema has no tenant/org column at all): - A JWT with no exp claim decoded successfully forever - the documented VITE_JWT_TOKEN demo credential had no expiry so it works without rotation, per its own prior docstring. jwt.decode() now requires exp; a token missing it is rejected the same as an invalid signature. - role was decoded into g.user but never checked by any route, so a viewer-role token could call every write endpoint (scan trigger, every AI endpoint) exactly like an admin token. verify_jwt() now requires a recognized role (viewer/operator/admin) on every accepted token, and rejects any non-GET/HEAD request from a viewer token with 403 - enforced once, in the middleware, so it covers every current and future write route uniformly rather than needing a per-route check. Confirmed this still holds under OPENSHIELD_PUBLIC_DEMO=true - demo mode only ever widens read access, never write. - POST /api/scans/trigger accepted an arbitrary caller-supplied subscription_id with no authorization check - any operator/admin token could trigger a scan against any subscription. Added an optional OPENSHIELD_AUTHORIZED_SUBSCRIPTIONS allowlist (issue #294's own suggested interim for a single-tenant deployment); left unset, behavior is unchanged from today, but the API now logs a loud startup warning when it is, matching the existing ALLOWED_ORIGINS/JWT_SECRET convention in the same function. - scripts/generate_demo_jwt.py minted a permanent, no-expiry token by design. It now requires an expiry (DEMO_JWT_TTL_HOURS, default 24h) and its docstring/output no longer claim the token works without rotation. - docs/api-reference.md and .env.example document the new requirements and the allowlist variable. Deliberately left untouched: frontend/src/App.jsx's local-dev dev-local-token bootstrap. It is not a valid signed JWT (verify_jwt() would reject it outright outside demo mode) and only ever worked because OPENSHIELD_PUBLIC_DEMO bypasses the JWT check for GET - removing it without a replacement login flow would break local dev for every contributor while fixing nothing real, since it was never a working credential to begin with. The actual leaked-credential risk is VITE_JWT_TOKEN, which is what this commit bounds with expiry and role. Verified: full backend suite (796 passed, 5 skipped - pre-existing/ environment-only, unrelated - confirmed identical skip count against unmodified dev), including all pre-existing tests/test_auth.py and tests/test_async_scan_persistence.py cases (every existing token fixture in this repo already carried role+exp, so nothing broke). New coverage in tests/test_auth.py (missing-exp, missing-role, unrecognized-role, viewer-read-allowed, viewer-write-rejected including under demo mode, operator/admin-write-allowed) and the new tests/test_subscription_authorization.py (unset allowlist preserves today's behavior, allowlisted/non-allowlisted subscriptions, case-insensitive comparison, blank-entry handling, startup-warning presence/absence). ruff check and format --check clean. Signed-off-by: Parth J Rohit <parthrohit60@gmail.com> Signed-off-by: parthrohit22 <parthrohit60@gmail.com>
…les (AZ-IDN-016-025) (#279) * feat: complete enterprise data protection rules Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * style: format storage rule tests Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * fix: satisfy rule validation and refresh image packages Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * feat: implement enterprise privileged access and identity security rules (AZ-IDN-016-025) Add 10 new rules covering privileged identity security for Microsoft Entra ID: - AZ-IDN-016: Privileged user missing phishing-resistant MFA (CRITICAL) - AZ-IDN-017: Global Administrator permanently assigned outside PIM (HIGH) - AZ-IDN-018: Privileged role assigned outside PIM (HIGH) - AZ-IDN-019: Stale privileged account retains active access (HIGH) - AZ-IDN-020: No emergency access accounts detected (HIGH) - AZ-IDN-021: Legacy authentication protocols not blocked (HIGH) - AZ-IDN-022: No MFA requirement for Azure Management (HIGH) - AZ-IDN-023: Identity Protection risk policies disabled (MEDIUM) - AZ-IDN-024: Service principals excluded from MFA enforcement (MEDIUM) - AZ-IDN-025: Privileged role-assignable group has no owner (MEDIUM) Add 5 new Graph API collectors to azure_client.py: - get_privileged_role_members, get_privileged_users_mfa_methods, get_pim_role_assignments, get_identity_protection_policies, get_privileged_groups Add MockAzureClient support for all new collectors (fully offline tests). Add 58 tests in test_rules_identity_priv.py covering compliant, violating, empty inventory, API failure (None), and edge cases. Add 10 remediation playbooks (fix_az_idn_016.sh through fix_az_idn_025.sh). Add compliance mappings for CIS Azure 2.0.0, NIST CSF, ISO 27001, SOC 2. Add docs/rules-reference.md entries for all 10 rules. Closes #258 Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * fix(scanner): correct privileged identity evaluation Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * fix(scanner): address review blockers in privileged access and identity rules PIM detection (IDN-017, IDN-018): - Collector now queries both roleAssignmentSchedules (Active) and roleEligibilitySchedules (Eligible) so eligible assignments are actually detected; previously only Active schedules were fetched - principalId/principalType tracked for all principal types (users, groups, service principals), not only users Identity Protection (IDN-023): - Replace unreliable direct policy endpoints with CA-policy inspection: checks conditions.userRiskLevels / signInRiskLevels on enabled policies CA policy user scope (IDN-021, IDN-022): - _covers_all_users() helper added; policies that target a subset of users no longer suppress a tenant-wide finding Workload identity exclusion (IDN-024): - Fix excludeServicePrincipals check: the field holds SP IDs, not All; rewrite to use includeServicePrincipals presence and All-exclude logic Collection failures (Cosmos DB, Redis cache): - get_cosmos_accounts / get_managed_caches return None on failure instead of []; callers log a warning and skip rather than treating missing inventory as compliant Enum normalization (AZ-STOR-009, AZ-DB-007): - Replace raw str() calls with enum_str() for SDK enum fields - Fix immutability retention property: period_since_creation_in_days -> immutability_period_since_creation_in_days Test fixtures updated to match new API contracts (principalId, deep-merge of CA policy conditions, correct immutability property name). CI: add chromadb CVE-2026-45830 and CVE-2026-45833 to pip-audit ignore list (no patched version; removal tracked in PR #317). Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * fix(tests): update vector-store skip guard to check bm25_index.json The skip condition checked for the ai/vectorstore/ directory, which can exist from a previous build without the BM25 index file. After PR #317 replaced chromadb with a JSON BM25 index, the test ran and raised VectorStoreNotBuilt instead of skipping. Changed skipif to check for the actual index file so the class is skipped correctly when the index has not been built. Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * fix(lint): shorten E501 line in test_ai_hallucination_guard.py Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> --------- Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
) * fix(api): rate-limit /ready and /metrics, expose db pool telemetry Closes the remaining scope of #296. The connection-checkout leak itself was already fixed by #306 (g.db + the teardown handler, with a real PostgreSQL-backed integration test proving connections balance across repeated readiness probes). What was still open: /ready and /metrics are unauthenticated by design (probe/scrape endpoints must never require a token), which also made them the one place an unauthenticated caller could trigger repeated pooled-connection work with no rate limiting at all, and there was no visibility into how close the pool was to exhaustion before it happened. - Add api.observability.probe_rate_limit: an in-memory, per-process, per-source-IP rate limiter for probe/scrape endpoints. Deliberately not the existing Postgres-backed api.rate_limit.rate_limit, which would add a database round trip (and a second, separately-tracked pooled connection under its own g.db) to the exact endpoint whose job is to protect the database from overload. The connection pool it guards is itself process-local under Gunicorn's multi-worker model, so a per-process budget is the matching granularity, not a weaker substitute for a shared one. Wired onto /ready (budget of 5 per 10s per source IP, half the default DB_POOL_MAX_CONN) and /metrics (20 per 10s, generous for normal Prometheus scrape intervals). The check runs before the view body, so a rejected request never reaches the database work it would otherwise trigger. - Add api.models.finding.get_pool_stats(): a point-in-time snapshot of the shared pool's in-use/idle/max-connection counts and utilization percentage. Reports only counts - never the DSN, host, or credentials - so it's safe on a public surface. Returns zeroed stats before any connection has been made instead of raising. - Wire get_pool_stats() into three new Prometheus gauges (openshield_db_pool_connections_in_use/idle/max), refreshed lazily on every /metrics scrape via a provider callback registered from api/app.py - api/observability.py stays free of project imports (its own documented constraint, since the worker reuses it too) by never importing api.models.finding directly. - Document in docs/deployment/render.md that Render's Blueprint format has no path-based access control, so the in-app rate limiter is a defense-in-depth backstop, not a substitute for restricting network reachability to /ready and /metrics at whatever reverse proxy/CDN/ WAF fronts a real deployment - that configuration is operational, outside what render.yaml can express. New tests in tests/test_readiness_hardening.py cover: the rate limiter's budget/window/per-IP-isolation/testing-bypass/key-pruning behavior in isolation, /ready actually rejecting a source once its budget is spent without touching the database for the rejected request, get_pool_stats()'s zero/nonzero/never-leaks-the-dsn behavior, and /metrics rendering the three new gauges (and surviving a broken stats provider without failing the whole scrape). Verified: full backend suite (796 passed, 3 skipped - pre-existing, unrelated), including tests/test_observability.py's real PostgreSQL-backed readiness-leak test run against a local Postgres instance to confirm the new decorator doesn't disturb #306's fix; ruff check and format --check clean. Signed-off-by: Parth J Rohit <parthrohit60@gmail.com> Signed-off-by: parthrohit22 <parthrohit60@gmail.com> * fix(api): restore dropped constant, bound probe-limiter memory Two fixes on top of the dev merge that landed on this branch: 1. The merge into dev (which now carries #294/#320's own changes to api/app.py) dropped this branch's _READY_MAX_REQUESTS_PER_WINDOW constant definition while keeping its usage on the /ready route, leaving api/app.py with an undefined name that only surfaced at create_app() call time (ruff's F821 caught it as CI's first failure; Backend Tests failed for the same underlying reason). Restored the constant and its comment. 2. m-khan-97's review: probe_rate_limit's per-key cleanup only ever prunes the exact (address, path) key the current request touches. A caller that continuously rotates its source address - or a spoofed forwarded address wherever the trusted-proxy boundary is misconfigured - creates a new one-shot dictionary entry per address that's never revisited and therefore never pruned, making the limiter's own tracking dict an unbounded memory sink. Fixed with the two things asked for: - A periodic global sweep (every _PROBE_SWEEP_INTERVAL calls, not every call - a full-dict scan per request would defeat the point of a cheap in-memory limiter) that prunes every key whose hits have all expired, not just the current request's key. - A hard cap (_PROBE_MAX_TRACKED_KEYS) on distinct tracked keys, with deterministic least-recently-touched eviction via an OrderedDict instead of the previous plain dict - every hit (including one that just survives a sweep) moves its key to the end, so eviction always drops the coldest entry first. Also added the suggested (non-blocking) Retry-After header on 429. New tests in tests/test_readiness_hardening.py cover exactly the scenario m-khan-97 described: many distinct one-shot addresses, advance past the window, trigger cleanup through different addresses, and prove the stale keys are gone and the map stays bounded. Plus direct hard-cap/LRU-eviction tests and the Retry-After header. Verified: full backend suite (862 passed, 5 skipped - pre-existing/ environment-only), including all 17 tests in tests/test_readiness_hardening.py and the pre-existing tests/test_auth.py / tests/test_observability.py suites unaffected. ruff check and format --check clean. Signed-off-by: Parth J Rohit <parthrohit60@gmail.com> Signed-off-by: parthrohit22 <parthrohit60@gmail.com> * fix(api): address TFT444's 3 probe-limiter findings 1. move_to_end(key) ran on every request including rejected ones, so an attacker hammering an already-exhausted key kept it permanently at the back of the eviction order while quiet, legitimate keys drifted toward the front and got evicted instead - defeating the memory cap's actual purpose for exactly the caller it exists to bound. Now only a request that counts against the budget (allowed) refreshes a key's position; a rejected request leaves it wherever it already was. 2. _probe_hits is one dict shared by every probe_rate_limit-decorated endpoint, but the periodic sweep applied whichever endpoint's request happened to trigger it - its own window_seconds - to every tracked key regardless of which endpoint's window actually applies to it. Dormant today since /ready and /metrics both default to the same 10s window, but the first endpoint added with a different one would have caused premature resets or lingering stale entries across every other endpoint's keys. Added _ProbeEntry to carry window_seconds alongside each key's own hits deque, so the sweep (and the per-call prune) always uses the window that key was actually registered under. 3. Documented the ProxyFix(x_for=1) trust boundary explicitly in api/app.py: what it assumes (Render's edge is the only thing able to append to X-Forwarded-For before this process sees it) and what breaks if that's violated (a directly-reachable origin lets a caller set their own forwarded IP per request, which is equivalent to no per-IP rate limiting at all for every control that depends on request.remote_addr). New regression tests for both behavioral fixes: one proves a caller hammering an exhausted key doesn't stay artificially warm while a quiet legitimate key gets evicted in its place; the other runs two endpoints with different window_seconds sharing the tracked-key dict and proves a sweep triggered by one doesn't misapply its window to the other's entries. Verified: full backend suite (864 passed, 5 skipped - pre-existing/ environment-only), all 19 tests in tests/test_readiness_hardening.py including the 2 new ones, ruff check and format --check clean. Signed-off-by: Parth J Rohit <parthrohit60@gmail.com> Signed-off-by: parthrohit22 <parthrohit60@gmail.com> --------- Signed-off-by: Parth J Rohit <parthrohit60@gmail.com> Signed-off-by: parthrohit22 <parthrohit60@gmail.com>
Bumps the npm_and_yarn group with 1 update in the /frontend directory: [postcss-selector-parser](https://github.com/postcss/postcss-selector-parser). Updates `postcss-selector-parser` from 6.1.2 to 6.1.4 - [Release notes](https://github.com/postcss/postcss-selector-parser/releases) - [Changelog](https://github.com/postcss/postcss-selector-parser/blob/main/CHANGELOG.md) - [Commits](postcss/postcss-selector-parser@v6.1.2...6.1.4) --- updated-dependencies: - dependency-name: postcss-selector-parser dependency-version: 6.1.4 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the npm_and_yarn group with 1 update in the /frontend directory: [browserslist](https://github.com/browserslist/browserslist). Updates `browserslist` from 4.28.2 to 4.28.9 - [Release notes](https://github.com/browserslist/browserslist/releases) - [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md) - [Commits](browserslist/browserslist@4.28.2...4.28.9) --- updated-dependencies: - dependency-name: browserslist dependency-version: 4.28.9 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* feat(frontend): add API request timeout support Signed-off-by: Shaurya K Sharma <shauryaksharma24@gmail.com> * fix(frontend): preserve transient API fallback semantics Signed-off-by: Shaurya K Sharma <shauryaksharma24@gmail.com> --------- Signed-off-by: Shaurya K Sharma <shauryaksharma24@gmail.com>
* website: migrate to Astro + Decap CMS with GitHub Pages pipeline Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * website: commit src/lib, bump astro to patched 7.x, drop obsolete CI job Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * website: build hero legend and rule info with DOM APIs, no innerHTML Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * fix(website): resolve manual test findings across hero, docs, feed and articles Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * feat(website): complete site and automate Pages deployment Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * fix(website): keep CMS optional and resolve CodeQL Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * ci: include website validation in required summary Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * style(ci): keep website gate comment concise Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * fix(ci): preserve container workflow contract Signed-off-by: ritiksah141 <ritiksah141@gmail.com> --------- Signed-off-by: ritiksah141 <ritiksah141@gmail.com>
* feat: complete enterprise data protection rules Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * fix: satisfy rule validation and refresh image packages Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * fix(scanner): address review blockers in enterprise data-protection rules - Move AZ-STOR-009 opt-in check from BlobContainer (no ARM tags) to the parent storage account, which exposes tags via the SDK; all containers under a tagged account are now evaluated for immutability. - Replace incorrect NIST mapping A.12.4.1 (ISO 27001) on AZ-DB-007 with PR.PT-1 across az_db_007.py, nist_csf.json, and rules-reference. - Add executable az CLI commands to fix_az_cache_001, fix_az_cosmos_001, fix_az_cosmos_002, fix_az_db_005, fix_az_db_006, and fix_az_db_007 playbooks; each validates the target and requires APPLY confirmation before modifying any Azure resource. - Update storage-protection-controls.md to document the account-level tagging scope for AZ-STOR-009. Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * fix(scanner): check immutability tag on container, not account (AZ-STOR-009) The policy_required guard was placed at the account level, but the oshield:immutability-required tag is set per container. Moving the check inside the container loop allows containers with the tag to be evaluated regardless of whether the parent account carries it. Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * fix(scanner): check immutability tag on container or parent account (AZ-STOR-009) The policy_required guard was placed at the account level only, but the oshield:immutability-required tag may be set per-container or per-account. Now uses OR logic: a container is evaluated if the account carries the requirement tag (protecting all containers) OR if the container itself carries it (per-container opt-in). Both cases were previously broken: the account-level check did not reach container-tagged resources, and no per-container check existed at all. Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * fix(scanner): address storage rule correctness gaps in AZ-STOR-006/007/008 - AZ-STOR-006: treat allow_shared_key_access=None as insecure (Azure documents unset as equivalent to True); only False is compliant - AZ-STOR-007: treat minimum_tls_version=None as TLS 1.0 (Azure default); use enum_str() instead of str() to handle SDK enum objects correctly - AZ-STOR-008 playbook: fix Key Vault URI parsing; the previous bash expansion passed the wrong segments to --encryption-key-vault and --encryption-key-name; now splits vault URI, key name, and optional key version correctly - ci.yml: remove CVE-2026-45830 and CVE-2026-45833 pip-audit exclusions (chromadb CVEs unrelated to this PR; resolved by PR #317) Adds regression tests for None-as-default behavior and SDK enum handling in AZ-STOR-006 and AZ-STOR-007 (22 storage tests, all passing). Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * fix(scanner): address all review feedback and CI failures for PR #278 Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> --------- Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
…336) * build: lock Python runtime and development dependencies with hashes Signed-off-by: Muhammad Ibrahim <135441675+m-khan-97@users.noreply.github.com> * fix(ci): isolate lock tooling outside the source checkout Signed-off-by: Muhammad Ibrahim <135441675+m-khan-97@users.noreply.github.com> --------- Signed-off-by: Muhammad Ibrahim <135441675+m-khan-97@users.noreply.github.com>
Signed-off-by: ritiksah141 <ritiksah141@gmail.com>
…attestations (#339) Signed-off-by: Muhammad Ibrahim <135441675+m-khan-97@users.noreply.github.com>
Signed-off-by: ritiksah141 <ritiksah141@gmail.com>
…ion (#294) (#345) * fix(auth): remove dashboard bearer token and add OIDC token verification (#294) Frontend - remove the VITE_JWT_TOKEN bootstrap and dev-local-token fallback - keep bearer tokens in memory only and purge legacy localStorage tokens - fail CI if a JWT-shaped value reaches the public bundle API - move token verification into api/auth.py with two modes: shared_secret (HS256, now also requires sub, optional iss/aud) and oidc (JWKS-verified asymmetric tokens with issuer, audience, expiry, issued-at, subject, tenant allowlist and IdP app-role mapping) - refuse HS256/none in oidc mode, fail closed with 503 when JWKS is unreachable, and refuse to start on incomplete oidc configuration - warn when shared_secret mode runs in production Docs - authentication setup, containment checklist and JWT_SECRET rotation - demo JWT script is now short-lived and for API testing only Signed-off-by: parthrohit22 <parthrohit60@gmail.com> * fix(auth): resolve SAST and credential-scan findings Rename the shared-secret mode constant so the credential scan does not read it as a hardcoded secret, generate the test signing secret at run time, and reword rejection log messages flagged by Semgrep. Signed-off-by: parthrohit22 <parthrohit60@gmail.com> --------- Signed-off-by: parthrohit22 <parthrohit60@gmail.com>
) * fix(scanner): rebase compute-rule corrections onto current dev Signed-off-by: parthrohit22 <parthrohit60@gmail.com> * test(scanner): drop duplicated AZ-CMP-007 test block from bad rebase A stale copy of the AZ-CMP-007 helpers and tests survived the dev rebase, sitting ahead of the AZ-CMP-004 assessment tests. Its local `def _subnet_id(name)` shadowed the module-level `_subnet_id(vnet_name, subnet_name)`, breaking seven AZ-CMP-001 tests with "takes 1 positional argument but 2 were given", and it carried two stray `f[...]` asserts that tripped ruff F811/F821 (11 errors). The canonical AZ-CMP-007 block at end of file (renamed helper `_jit_subnet_id`, no stray asserts) is kept. ruff clean; test_rules_compute.py 56 passed. Signed-off-by: parthrohit22 <parthrohit60@gmail.com> * style(tests): add missing blank line between mock_azure methods `ruff format --check` (run alongside `ruff check` in the Lint job) flagged tests/helpers/mock_azure.py — set_vm_patch_status, added earlier in this PR, had no blank line before it. The earlier `ruff check` F811 failure had masked this. Signed-off-by: parthrohit22 <parthrohit60@gmail.com> --------- Signed-off-by: parthrohit22 <parthrohit60@gmail.com>
…273) * feat: add rule AZ-CMP-005 Trusted Launch (Secure Boot + vTPM) check Adds a Compute scan rule that flags Generation 2 VMs which do not have Trusted Launch fully enabled (security type TrustedLaunch with both Secure Boot and vTPM on). Without them, unsigned or malicious code can run during boot and persist beneath the OS, evading OS-level antimalware and EDR. Generation 1 VMs cannot use Trusted Launch and are treated as NOT_APPLICABLE: a VM's list_all() representation does not carry its Hyper-V generation, so the rule resolves the OS disk's hyper_v_generation to confirm a VM is Gen2 before flagging, and never raises a false finding against Gen1 hardware. A security type already declared as TrustedLaunch is itself Gen2-only, so a Secure-Boot/vTPM-off VM there is flagged without a disk lookup. Confidential VMs (which provide Secure Boot and vTPM by construction) are out of scope, and a VM whose generation cannot be confirmed is left unflagged rather than risking a Gen1 false positive. Includes the remediation playbook (playbooks/cli/fix_az_cmp_005.sh) and maps the rule across the four compliance frameworks: NIST CSF PR.DS-6, ISO 27001 A.12.5.1, and SOC 2 CC6.8. CIS uses the repository's existing N/A convention (as in AZ-KV-001) because CIS Azure Foundations 2.0.0 has no dedicated Trusted Launch recommendation. Adds six unit tests covering the compliant, non-compliant Gen2, TrustedLaunch-declared-but-vTPM-off, Gen1 NOT_APPLICABLE, unknown-generation, and Confidential-VM cases. Closes #269 Signed-off-by: shariqueahmad108-ship-it <shariqueahmad108@gmail.com> * fix(playbook): guard fix_az_cmp_005.sh args under set -u Use ${1:-}/${2:-} so running the playbook with missing arguments prints the usage message and exits 1, instead of crashing on an unbound variable under `set -euo pipefail`. Matches the convention in fix_az_net_016.sh. Addresses review feedback from @Vishnu2707 on #273. Signed-off-by: shariqueahmad108-ship-it <shariqueahmad108@gmail.com> --------- Signed-off-by: shariqueahmad108-ship-it <shariqueahmad108@gmail.com>
* fix(scanner): resolve COR-001-004 scanner correctness issues (#151) Signed-off-by: safidnadaf <safidnadaf25@gmail.com> * fix(scanner): normalise Azure SDK enum fields in AZ-NET-003 and AZ-DB-002 Addresses review feedback on PR #163: - Add a shared enum_str() helper in azure_client.py that safely unwraps Azure SDK enum fields via .value, since str(enum_member) yields e.g. 'SecurityRuleDirection.INBOUND' rather than 'Inbound' and silently breaks naive string comparisons against real SDK objects. - AZ-NET-003: normalise direction, access, and source_address_prefix through enum_str() so real SecurityRuleDirection/SecurityRuleAccess enum values are detected correctly, not just plain-string mocks. - AZ-DB-002: normalise the auditing policy state through enum_str() so a real BlobAuditingPolicyState.ENABLED value is not mistaken for disabled (false positive) or vice versa. - AZ-DB-002: malformed ARM IDs are now logged explicitly instead of silently skipped. - AZ-NET-003: the matched plural source_address_prefixes entry is now included in finding metadata. - Add regression tests using real azure-mgmt-network / azure-mgmt-sql SDK model classes (SecurityRule, SecurityRuleDirection, SecurityRuleAccess, ServerBlobAuditingPolicy, BlobAuditingPolicyState) rather than only SimpleNamespace/string-backed mocks, per SHAURYAKSHARMA24's review. - Sync branch with upstream dev (v0.3.0) and apply current ruff format gate, per ritiksah141's review. Signed-off-by: safidnadaf <safidnadaf25@gmail.com> * fix: remove duplicate _diagnostic_settings init line Signed-off-by: safidnadaf <safidnadaf25@gmail.com> * test: add identity rule regression coverage Signed-off-by: safidnadaf <safidnadaf25@gmail.com> * fix: improve AZ-NET-003 scanner correctness Signed-off-by: safidnadaf <safidnadaf25@gmail.com> * fix: resolve ruff lint errors Signed-off-by: safidnadaf <safidnadaf25@gmail.com> * test: restore az_net_016/017 imports dropped in merge Signed-off-by: safidnadaf <safidnadaf25@gmail.com> * chore: trigger CI Signed-off-by: safidnadaf <safidnadaf25@gmail.com> * fix: add compliant plural-port test, drop unrelated identity test reformatting Addresses parthrohit22's review: adds a compliant-case regression test for the destination_port_ranges fix in AZ-NET-003, and resets tests/test_rules_identity.py to dev's formatting, keeping only the one genuine new test (test_idn_007_disabled_user_without_mfa_returns_no_findings). Signed-off-by: safidnadaf <safidnadaf25@gmail.com> * style: apply ruff format to az_net_003.py Signed-off-by: safidnadaf <safidnadaf25@gmail.com> * fix(scanner): AZ-DB-002 emits indeterminate LOW finding on failed policy lookup Addresses TFT444's review: previously a failed auditing-policy lookup (API/auth failure) was silently skipped. This aligns with the AZ-CMP-002 convention by emitting a LOW-severity finding with metadata.determination = 'indeterminate' instead, so the verification gap stays visible in scan output rather than disappearing silently. Signed-off-by: safidnadaf <safidnadaf25@gmail.com> --------- Signed-off-by: safidnadaf <safidnadaf25@gmail.com> Co-authored-by: safidnadaf <safidnadaf@users.noreply.github.com>
* feat: add VM Scale Set inventory collector and rule AZ-CMP-005 Adds AzureClient.get_virtual_machine_scale_sets() (list_all across the subscription, following the get_virtual_machines() pattern) and its MockAzureClient test double. Ships AZ-CMP-005 as the first rule to use it: flags VMSS network interface configurations that provision a public IP with no NSG attached, the VMSS-template equivalent of AZ-CMP-001's per-VM NIC check. Detection reads the network interface configuration template directly (network_interface_configurations[].ip_configurations[] / .network_security_group) rather than resolving separate NIC resources, since a VMSS profile embeds these settings inline. Includes a remediation playbook (az vmss update --set on the network profile, with an explicit warning about the required instance upgrade), collector and rule tests, and compliance framework mappings. CIS is mapped to N/A-CMP-005 following the repository's established convention for the same real control (7.1, owned by AZ-CMP-001) applied to a second resource type, since the one-CIS-ID-per-rule convention doesn't allow reusing 7.1 directly. Closes #271 Signed-off-by: Dipesh Ray <dipesh.ray.g@gmail.com> * fix: renumber AZ-CMP-005 to AZ-CMP-006, fix subnet-NSG false positive AZ-CMP-005 collided with #273 (Trusted Launch check), opened a day before this PR and already claiming that rule ID. Renumbered the rule file, playbook, tests, and all four compliance framework entries to AZ-CMP-006. Also fixes a false-positive gap flagged in review: the rule only checked for an NSG on the VMSS network interface configuration itself, missing the case where the NSG is attached at the subnet level instead. A VMSS whose NIC has no NSG but deploys into a subnet that does have one was being incorrectly flagged. Now resolves each network interface configuration's subnet (via the existing get_virtual_networks() collector, no new collector needed) and treats either a NIC-level or subnet-level NSG as compliant, matching how AZ-NET-010 already reads subnet.network_security_group. Added SOC2 to the rule's own FRAMEWORKS dict (was previously only in soc2.json, inconsistent with how several other rules, e.g. AZ-KV-006, already include it directly). Addresses review feedback from TFT444 and m-khan-97 on #275. Signed-off-by: Dipesh Ray <dipesh.ray.g@gmail.com> * fix: distinguish unresolved subnet from confirmed no-NSG in AZ-CMP-006 subnet_nsgs.get(subnet_id, False) treated an unresolved subnet reference (VNet collection failure, missing permissions, or a subnet ID this scan never saw) identically to a resolved subnet confirmed to have no NSG, reintroducing the false-positive the previous commit was meant to fix. Now distinguishes three states per ip_configuration: resolved+has NSG (compliant), resolved+no NSG (confirmed non-compliant, HIGH), and unresolved (indeterminate, LOW), mirroring the confirmed/indeterminate pattern already established in az_cmp_002.py. Also normalizes subnet IDs to lowercase before comparison, since Azure resource IDs are case-insensitive and the two APIs involved (VMSS network profile vs. VNet subnets) aren't guaranteed to return matching casing. Adds regression tests for an unresolved subnet (VNet collection returns empty) and for a differently-cased subnet ID match. Addresses review feedback from ritiksah141 on #275. Signed-off-by: Dipesh Ray <dipesh.ray.g@gmail.com> * fix: resolve _subnet_id name collision from rebasing onto AZ-CMP-007 AZ-CMP-007 (merged into dev while this branch was in review) and this branch's subnet-NSG fix for AZ-CMP-006 each defined their own _subnet_id() helper with different signatures. Landing them in the same file after the rebase left two same-named functions, and the second definition silently shadowed the first, breaking the AZ-CMP-007 subnet-exposure test. Renamed this branch's helper to _vnet_subnet_id() to remove the collision. Signed-off-by: Dipesh Ray <dipesh.ray.g@gmail.com> * fix: three real gaps in AZ-CMP-006 flagged by review - Only ip_configurations that actually carry a public IP are checked against subnet NSG state. A non-primary ip_config with no public IP is not internet-reachable, so its subnet must not be able to force a finding on an otherwise-compliant net_config. - Removed the break after the first non-compliant net_config. A VMSS with several exposed configs now gets one finding per config instead of silently hiding every attack surface after the first. - Indeterminate findings now carry vnets_collected in metadata, so a persistent zero across many findings is visible as a VNet-collection problem instead of reading as an ordinary per-subnet indeterminate result. Signed-off-by: Dipesh Ray <dipesh.ray.g@gmail.com> * fix: require all public ip_configs protected, not any, in AZ-CMP-006 A net_config can carry several public ip_configs on different subnets. 'any(status is True for status in subnet_statuses)' treated the whole net_config as compliant if any one of them was subnet -protected, even when another public ip_config on the same net_config was still exposed on an unprotected subnet. Compliance now requires every public ip_config to be protected. A confirmed-unprotected ip_config makes the net_config non-compliant regardless of whether another ip_config on it is merely unresolved, matching the existing confirmed-beats-indeterminate severity model. Added a regression test: two public ip_configs on different subnets, one protected and one not, expecting one confirmed HIGH finding. Signed-off-by: Dipesh Ray <dipesh.ray.g@gmail.com> --------- Signed-off-by: Dipesh Ray <dipesh.ray.g@gmail.com>
* Add enterprise AKS and workload security controls Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * Address AKS security review feedback Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * Harden AKS tenant evidence isolation Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * Fix trusted registry prefix boundaries Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * Fix AKS policy namespace exclusions Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * Fix Linux dependency lock completeness Signed-off-by: ritiksah141 <ritiksah141@gmail.com> * Clarify AKS secret remediation guidance Signed-off-by: ritiksah141 <ritiksah141@gmail.com> --------- Signed-off-by: ritiksah141 <ritiksah141@gmail.com> Co-authored-by: Vishnu Ajith <86302373+Vishnu2707@users.noreply.github.com> Co-authored-by: Muhammad Ibrahim <135441675+m-khan-97@users.noreply.github.com>
* fix(deps): bump pip to 26.2.1 (Dependabot alert #21) pip < 26.2.0 would incorrectly handle doubly-encoded package URLs from indexes, allowing a malicious index to serve unexpected packages. This updates requirements-lock.txt to pin pip==26.2.1 with the correct hash. Fixes: https://github.com/OWASP/openshield/security/dependabot/21 Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * fix(deps): update lock_dependencies.py pip version guard to 26.2.1 Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> * fix(deps): upgrade pip-tools to 7.6.1 for pip 26.2.1 compatibility pip-tools 7.5.3 imports stdlib_pkgs from pip._internal.utils.compat, which was removed in pip 26.2.1. pip-tools 7.6.1 dropped that internal dependency (fixed in 7.4.1). Update the source pin in requirements-lock.in, the compiled hash in requirements-lock.txt, and the version guard in lock_dependencies.py to keep all three in sync. Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com> --------- Signed-off-by: Tanvir Farhad <tamimtarafder12@gmail.com>
Vishnu2707
requested review from
SHAURYAKSHARMA24,
parthrohit22,
ritiksah141 and
vogonPrayas
as code owners
September 20, 2026 20:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Promotes dev to main for the v0.4.0 release. Captures all work since v0.3.0 — enterprise rule packs, OWASP project migration, scan durability hardening, AI layer foundations, signed releases, supply chain security, frontend accessibility, and dependency security fixes.
Type of change
Key changes
Testing
Related issues
Closes #263, #303
Checklist