Skip to main content

WAF rules as code — manage rules across providers declaratively

Project description

octorules

WAF rules as code — manage rules across providers declaratively

In the vein of infrastructure as code, octorules provides tools & patterns to manage WAF and security rules as YAML files. The resulting config can live in a repository and be deployed just like the rest of your code, maintaining a clear history and using your existing review & workflow.

octodns manages DNS records, but can't touch WAF rules. octorules fills that gap — one YAML file per domain/policy, plan-before-apply, fail-fast on errors.

Provider ecosystem

octorules is provider-agnostic. Each provider is a separate package:

Package Provider Status
octorules-cloudflare Cloudflare Rules (23 phases) Stable
octorules-aws AWS WAF v2 (4 phases) Beta
octorules-google Google Cloud Armor (4 phases) Beta
octorules-azure Azure WAF — Front Door & App Gateway (3 phases) Alpha
octorules-bunny Bunny.net Shield WAF (4 phases) Alpha

Getting started

Installation

Install the provider package for your WAF. This pulls in octorules core automatically:

pip install octorules-cloudflare    # Cloudflare (includes wirefilter expression engine)
pip install octorules-aws           # AWS WAF v2
pip install octorules-google        # Google Cloud Armor (includes cel-python)
pip install octorules-azure         # Azure WAF (Front Door / Application Gateway)
pip install octorules-bunny         # Bunny.net Shield WAF

Core only (offline lint, no provider):

pip install octorules

Configuration

Create a config file pointing at your zones:

# config.yaml
providers:
  cloudflare:
    token: env/CLOUDFLARE_API_TOKEN
  rules:
    directory: ./rules

zones:
  example.com:
    sources:
      - rules

The env/ prefix resolves values from environment variables at runtime — keep secrets out of YAML. This is the built-in secret handler; see Secret handlers for pluggable backends (Vault, AWS Secrets Manager, etc.).

Complete examples — the examples/ directory contains detailed config and rules files for all five providers (Cloudflare, AWS, Google, Azure, Bunny), including multi-provider setups, every config field, and every phase/rule format. Start there rather than writing config from scratch.

All keys under a provider section (except class and safety) are forwarded as keyword arguments to the provider constructor — octodns-style passthrough. See each provider's documentation for available settings.

Multi-provider setup

To manage rules across multiple providers, add each provider as a named section under providers: and assign zones to providers via targets:. When only one provider is configured, targets is auto-assigned and can be omitted. See examples/config.yaml for a full multi-provider config with all five providers.

Multi-target zones

A zone can target multiple providers of the same class (e.g. two Cloudflare accounts, or prod + staging). List multiple names under targets: — octorules plans and applies independently for each. Requires explicit class: since auto-discovery can't distinguish two instances of the same provider. See examples/config.yaml for a commented example.

Provider auto-discovery

Provider classes are auto-discovered via the octorules.providers entry-point group (installed provider packages register themselves). To override auto-discovery, set class: explicitly:

providers:
  custom:
    class: my_package.MyProvider
    api_key: env/MY_API_KEY

YAML includes

YAML files support !include directives to split large configs:

zones:
  example.com: !include zones/example.yaml
# rules/example.com.yaml
redirect_rules: !include shared/redirects.yaml

Includes resolve relative to the file containing the directive. Nested includes and circular include detection are supported. Includes are confined to the directory tree of the parent file.

Defining rules

Create a rules file for each zone. The filename must match the zone name used as the key under zones: in config.yaml, which maps to the provider's own concept of a "zone":

Provider Zone concept Example zone name Rules file
Cloudflare DNS domain example.com rules/example.com.yaml
AWS WAF Web ACL name my-web-acl rules/my-web-acl.yaml
Google Cloud Armor Security policy name my-security-policy rules/my-security-policy.yaml
Azure WAF WAF policy name my-waf-policy rules/my-waf-policy.yaml
Bunny Shield Pull zone name my-pull-zone rules/my-pull-zone.yaml

The mapping is: zones.<name> in config.yamlrules/<name>.yaml on disk → resolve_zone_id("<name>") at runtime, which resolves the name to the provider's internal ID.

Each rule requires a ref (stable identifier, unique within a phase) and an expression (provider-specific filter expression). Optional fields include description, enabled (defaults to true), action, and action_parameters. Phase names, available actions, and expression syntax are provider-specific — see your provider's documentation and the examples/rules/ directory for complete per-provider examples.

Rule-level metadata

Rules support an octorules: key for per-rule metadata that controls octorules behavior without affecting the provider API.

Ignoring rules — keep a rule in YAML (for documentation, version control, review) while skipping it during plan/sync:

waf_custom_rules:
  - ref: experimental-geo-block
    description: "Testing geo-block  not ready for production"
    expression: 'ip.geoip.country in {"RU" "CN"}'
    action: block
    octorules:
      ignored: true

Ignored rules are still validated and linted (catch errors before un-ignoring), but are invisible to the planner on both sides — they produce no ADD/MODIFY/REMOVE changes, and if the rule exists upstream it will not be deleted or overwritten. This matches the octodns convention: the rule can be edited manually on the provider without octorules interfering.

Targeting providers — in multi-provider or multi-target setups, restrict a rule to specific targets:

waf_custom_rules:
  # Only deploy to Cloudflare
  - ref: cf-specific-rule
    expression: 'http.request.uri.path matches "^/api/.*"'
    action: block
    octorules:
      included:
        - cloudflare

  # Deploy everywhere EXCEPT staging
  - ref: prod-only-rule
    expression: 'ip.src in $blocklist'
    action: block
    octorules:
      excluded:
        - cf-staging

included and excluded are mutually exclusive (matching octodns convention). Names match the provider config key (e.g. cloudflare, aws, cf-prod). Rules without included/excluded apply to all targets.

The octorules: key is always stripped before sending rules to the provider API.

Multi-line expressions

Complex expressions can use YAML block scalars (|-) for readability. octorules normalizes whitespace (collapsing newlines and indentation to single spaces outside quoted strings) before sending to the provider and before linting, so formatting is purely cosmetic:

waf_custom_rules:
  - ref: geo-block
    description: Block by country outside active regions
    action: block
    expression: |-
      (ip.geoip.asnum in {
        9009
        64080
      } and not ip.geoip.country in {
        "AT"
        "BE"
        "DE"
        "FR"
      })

Use |- (strip trailing newline) rather than | (preserves trailing newline).

Usage

octorules uses separate commands for planning and applying — like Terraform's plan/apply split. WAF rules have a high blast radius (a bad rule can block all traffic), so the two-step workflow forces an explicit review before changes reach the provider. This also enables CI patterns where plan runs on PR open (posting results as a PR comment) and sync runs on merge with checksum verification to catch drift.

# Preview changes (dry-run)
octorules plan --config config.yaml

# Apply changes
octorules sync --doit --config config.yaml

# Validate config only (no API calls, useful in CI)
octorules lint --config-only --config config.yaml

# Export existing rules to YAML
octorules dump --config config.yaml

# Lint rules files offline
octorules lint --config config.yaml

# Audit for IP overlaps, CDN conflicts, and zone drift
octorules audit --config config.yaml

Secret handlers

Config string values use handler/reference syntax to resolve secrets at load time. The built-in env handler resolves environment variables (env/MY_TOKEN$MY_TOKEN). You can add custom handlers for Vault, AWS Secrets Manager, GCP Secret Manager, etc.

Config-declared handlers

secret_handlers:
  vault:
    class: octorules_vault.VaultSecrets
    url: https://vault.internal
    token: env/VAULT_TOKEN           # bootstrap: resolved via env handler

providers:
  cloudflare:
    token: vault/secret/data/cf#token  # resolved via vault handler

Handler kwargs are resolved through already-registered handlers (env + entry-points), so you can bootstrap credentials with env/.

Entry-point discovery

Secret handlers can also be auto-discovered via the octorules.secret_handlers entry-point group:

# In your handler package's pyproject.toml
[project.entry-points."octorules.secret_handlers"]
vault = "octorules_vault:VaultSecrets"

Writing a secret handler

Subclass BaseSecrets from octorules.secret:

from octorules.secret import BaseSecrets, SecretsException

class VaultSecrets(BaseSecrets):
    def __init__(self, name, url="", token=""):
        super().__init__(name)
        self.client = VaultClient(url=url, token=token)

    def fetch(self, ref, source):
        try:
            return self.client.read(ref)
        except VaultError as e:
            raise SecretsException(f"Vault lookup failed for {ref!r}: {e}")

Resolution rules

  1. Split string on first /(prefix, reference)
  2. Look up prefix in the handler registry
  3. Found → call handler.fetch(reference, source_context)
  4. Not found → return string unchanged (paths like ./rules or https://... pass through safely)

Processors

Processors hook into the plan/sync pipeline to transform rules before planning and filter changes after planning. They're useful for injecting shared rules, enforcing policy, or suppressing changes across zones.

processors:
  add_standard_headers:
    class: my_package.StandardHeaderProcessor
    header_name: X-Frame-Options

zones:
  example.com:
    sources:
      - rules
    processors:
      - add_standard_headers

A processor is a Python class with two optional hooks:

  • process_desired(zone_name, desired, provider) — transform the desired rules dict before planning. Return the modified dict.
  • process_changes(zone_name, plan, provider) — transform the ZonePlan after planning. Return the modified plan.

Both default to no-op (pass-through). Processors run in the order listed. The class key is required; all other keys are forwarded as kwargs.

Built-in filters

octorules ships three ready-to-use processors in octorules.processor.filters:

Filter Description
PhaseFilter Include or exclude phases by name (include/exclude lists)
RefFilter Include or exclude rules by regex on the ref field
ChangeTypeFilter Block specific change types: ADD, REMOVE, MODIFY, REORDER

See examples/config.yaml for working examples of all three.

Zone discovery

Zones can be discovered automatically from providers that support it. Use '*' as a zone template in your config — octorules calls list_zones() on target providers at init time and expands the template for each discovered zone that has a matching YAML rules file. Explicit zone configs always take precedence. See the wildcard entry in examples/config.yaml.

Optional features

Providers declare optional feature support via a SUPPORTS class variable. The framework checks support before calling optional methods. Features include:

Feature Description Providers
custom_rulesets Account-level WAF rulesets (rule groups) Cloudflare, AWS
lists IP/ASN/hostname/redirect/regex lists (IP sets, regex pattern sets) Cloudflare, AWS
page_shield Content Security Policy management Cloudflare
zone_discovery Automatic zone enumeration via list_zones() Cloudflare, AWS, Google, Azure, Bunny

See each provider's documentation for feature details and YAML syntax.

Linting

octorules lint runs offline static analysis on your rules files — no API calls, no credentials needed. Lint rules are provider-registered; install a provider package to get its rules. See octorules lint in the CLI reference for flags and options.

Suppression comments work like shellcheck — add # octorules:disable=CF015 (comma-separated for multiple rules) before a rule to suppress specific findings. Audit findings use # octorules:accept=ip-overlap.

Core lint rules

Rule Severity Description
CORE002 WARNING Orphaned rules file (no matching zone in config)
CORE003 WARNING All rules in a phase are disabled (2+ rules, all enabled: false)
CORE004 WARNING Same ref string used in multiple phases within a zone
CORE006 INFO Rules file contains no actual rules (all phases empty)

Provider-specific rules (CF, WA, GA, AZ, BN prefixes) are documented in each provider's docs/lint.md.

Config validation (not lint rules)

These checks run at config load time and emit log warnings or raise errors. They are not lint diagnostics — they cannot be suppressed with # octorules:disable=... and do not appear in lint output.

Check Level Description
Duplicate YAML key ERROR Raises ConfigError on duplicate keys (silent data loss — last value wins)
Inverted safety thresholds WARNING Logs a warning when delete_threshold < update_threshold (deletes less restricted than updates)

CLI reference

octorules plan

Dry-run: shows what would change without touching the provider. Exit code 2 when changes are detected (with --exit-code). Output format and destination are controlled via manager.plan_outputs in the config file (defaults to text on stdout).

octorules plan [--zone example.com] [--phase redirect_rules] [--checksum] [--exit-code]

octorules sync --doit

Applies changes to the provider. Requires --doit as a safety flag. Atomic PUT per phase, fail-fast on errors.

octorules sync --doit [--zone example.com] [--phase redirect_rules] [--checksum HASH] [--force]
Flag Description
--doit Required safety flag to confirm changes should be applied
--checksum HASH Verify plan hasn't drifted since plan --checksum
--force Bypass safety threshold checks
--audit-log PATH Write JSON lines audit log of sync results
--format json Print structured JSON results to stdout (zone, status, synced phases, errors)

octorules rule

Browse and search the lint rule catalog.

octorules rule --all                  # List all rules
octorules rule CF                     # Filter by prefix
octorules rule CF201                  # Show one rule
octorules rule --all --format json    # JSON output

octorules dump

Exports existing provider rules to YAML files. Useful for bootstrapping or importing an existing setup.

octorules dump [--zone example.com] [--output-dir ./rules]

octorules lint

Lint rules files offline for errors, warnings, and style issues. Supports text, JSON, SARIF, and summary output.

octorules lint [FILE] [--config-only] [--format text|json|sarif] [--severity error|warning|info] [--plan free|pro|business|enterprise] [--rule RULE_ID] [--output PATH] [--exit-code]
Flag Description
FILE Lint a single rules file (no config needed). When omitted, uses the config file to discover all zones
--config-only Only validate config file structure (skip rules files)
--format Output format: text (default), json, sarif, summary
--severity Minimum severity to report (default: info)
--plan Plan tier for entitlement checks (default: enterprise)

When --plan is not specified, lint reads .zone_plans_cache.json (written automatically by plan, sync, and dump) for automatic per-zone tier detection. If neither --plan nor the cache provides a tier, enterprise is assumed (most permissive, fewest false positives). Add .zone_plans_cache.json to your .gitignore — it contains no secrets, just zone-to-tier mappings.

| --rule | Only check specific rule ID(s); can be repeated | | --output | Write results to a file instead of stdout | | --exit-code | Exit with 1 on errors, 2 on warnings (for CI) |

octorules audit

Audit rules for cross-rule IP overlaps, shadowed rules, CDN range conflicts, and cross-zone inconsistencies. Processes every *.yaml file in the rules directory (not just configured zones). No API credentials needed.

octorules audit [--check ...] [--severity error|warning|info] [--format text|json] [--output FILE] [--exit-code] [--cdn-timeout N] [--cdn-stale-days N]
Flag Description
--check Only run specific check(s); can be repeated (default: all)
--severity Minimum severity to report (default: info)
--format Output format: text (default), json, summary
--output Write results to a file instead of stdout
--exit-code Exit with 1 on errors, 2 on warnings (for CI)
--cdn-timeout Timeout in seconds for CDN range API fetches (default: 15)
--cdn-stale-days Warn if baked-in CDN ranges are older than N days (default: 60)

Checks:

  • ip-overlap -- Cross-rule and cross-list IP range overlaps within a zone.
  • ip-shadow -- Rules shadowed by broader rules in earlier phases (e.g. a rate-limit rule whose IPs are already blocked by a WAF rule).
  • cdn-ranges -- Rules that match known CDN provider IP ranges (Cloudflare, AWS CloudFront, Google Cloud, Bunny, Azure Front Door). Fetches fresh ranges from public APIs; falls back to baked-in data when offline. (Azure's list is scraped from the Microsoft Download Center page — the JSON URL rotates weekly.)
  • zone-drift -- Same CIDR treated differently across zones (e.g. blocked in zone A, allowed in zone B).

Acceptance comments suppress known findings (check names must be lowercase):

  # octorules:accept=zone-drift
  # octorules:accept=ip-overlap,cdn-ranges

octorules versions

Print versions of octorules and key dependencies.

octorules versions

Common flags

Flag Description
--config PATH Path to config file (default: config.yaml)
--zone NAME Process only specified zone(s); can be repeated (default: all)
--phase NAME Limit to specific phase(s); can be repeated
--scope SCOPE Scope: all (default), zones, or account
--debug Enable debug logging
--quiet Suppress all informational stdout output (plan tables, lint results, audit findings). Only errors and the exit code are reported. File output (--output) is unaffected
--syslog ADDRESS Send logs to syslog (host:port for UDP, or /path/to/socket)

Environment variables

Variable Effect
NO_COLOR Disable colored terminal output (any value, including empty)
FORCE_COLOR Force colored output even when stdout is not a TTY

NO_COLOR takes precedence over FORCE_COLOR. See https://no-color.org/.

Exit codes

Code Meaning
0 Success / no changes
1 Error (or lint errors found with --exit-code)
2 Changes detected (plan --exit-code) / lint warnings found (lint --exit-code)

After every command, a summary line is printed to stderr:

octorules plan: exit 0 (no changes) 0.3s

Tab completion

Tab completion is built in (bash, zsh, tcsh). Generate the completion script once and place it in the standard location:

# Bash
octorules completion bash > ~/.local/share/bash-completion/completions/octorules

# Zsh (add fpath+=~/.zfunc before compinit in .zshrc)
octorules completion zsh > ~/.zfunc/_octorules

Regenerate after upgrading octorules (new subcommands/flags).

Config reference

secret_handlers:                     # Optional — custom secret backends
  vault:
    class: octorules_vault.VaultSecrets  # Required: dotted class path
    url: https://vault.internal          # All other keys forwarded as kwargs
    token: env/VAULT_TOKEN               # Handler kwargs resolved via env + entry-points

providers:
  my_provider:                       # Provider name (any name works)
    token: env/API_TOKEN             # All keys forwarded to provider constructor
    class: my_package.MyProvider     # Optional: override auto-discovered provider
    safety:                          # Framework-owned (NOT forwarded to provider)
      delete_threshold: 30.0         # Max % of rules that can be deleted (default: 30)
      update_threshold: 30.0         # Max % of rules that can be updated (default: 30)
      min_existing: 3                # Min rules before thresholds apply (default: 3)
  rules:
    directory: ./rules               # Path to rules directory
  lists:
    directory: ./rules/custom_lists  # Path for externalized list items (default: {rules_dir}/custom_lists)

processors:
  my_proc:
    class: my_package.MyProcessor    # Required: dotted class path
    setting: value                   # All other keys forwarded as kwargs

manager:
  max_workers: 4                     # Parallel processing (default: 1)
  plan_outputs:                      # Config-driven plan output
    text:
      class: octorules.plan_output.PlanText
    html:
      class: octorules.plan_output.PlanHtml
      path: /tmp/plan.html           # Optional: write to file instead of stdout

zones:
  example.com:
    sources:
      - rules
    targets:
      - my_provider
    processors:
      - my_proc
    allow_unmanaged: false           # Keep rules not in YAML (default: false)
    always_dry_run: true             # Never apply changes (default: false)
    safety:                          # Per-zone overrides
      delete_threshold: 50.0

  '*':                               # Zone discovery template
    sources:
      - rules
    targets:
      - my_provider

Programmatic usage

The Manager class provides a Python API for all octorules operations:

from octorules import Manager

with Manager("config.yaml") as mgr:
    # Preview changes (returns exit code)
    rc = mgr.plan(exit_code=True)

    # Apply changes
    mgr.sync(force=True)

    # Lint specific zones
    mgr.lint(zones=["example.com"], severity="warning")

    # Export rules
    mgr.dump(output_dir="/tmp/rules")

All methods accept the same options as the CLI (zones, phases, scope, etc.) and return the same exit codes. The Manager handles provider/processor initialization and executor lifecycle.

How it works

  1. Plan — Reads your YAML rules, fetches current rules from the provider, computes a diff by matching rules on ref (phases), name (lists), or description (policies). Processors transform desired rules before diffing and filter changes after.
  2. Sync — Executes the plan in order: lists, policies, custom rulesets, then phases. Each phase uses an atomic PUT (full replacement of the phase ruleset). Fail-fast on errors.
  3. Dump — Fetches all rules from the provider and writes them to YAML files, stripping API-only fields (id, version, last_updated, etc.).

Performance (all parallelism controlled via manager.max_workers, default: 1):

  • Parallel phase fetching — phases within each scope are fetched concurrently.
  • Parallel phase apply — phase PUTs within a zone are applied concurrently during sync.
  • Parallel apply stages — list item updates, custom ruleset PUTs, and policy operations within each stage run concurrently.
  • Parallel zone processing — multiple zones are planned/synced concurrently.
  • Parallel zone ID resolution — zone name lookups run concurrently.
  • Concurrent account planning — account-level rules are planned in parallel with zone rules.
  • Scope-aware phase filtering — only zone-level phases are fetched for zone scopes, and only account-level phases for account scopes, eliminating wasted API calls.
  • Rules caching — YAML rule files are parsed once and cached for the duration of each run.

Safety features:

  • --doit flag — sync requires explicit confirmation.
  • Delete thresholds — blocks mass deletions above a configurable percentage.
  • Checksum verificationplan --checksum produces a hash; sync --checksum HASH verifies the plan hasn't changed.
  • Auth error propagation — authentication and permission errors fail immediately instead of being silently swallowed.
  • Failed phase filtering — phases that can't be fetched are excluded from planning to prevent accidental mass deletions.
  • Path traversal protection!include directives and file operations are confined to their expected directories.

How safety thresholds work

Safety thresholds prevent accidental mass changes. When a plan would delete or update more than a configurable percentage of existing rules in any phase, the sync is blocked. Defaults: delete_threshold: 30.0, update_threshold: 30.0, min_existing: 3 (thresholds only apply once a phase has at least this many rules). Can be set per-provider or per-zone — see Config reference. Override with --force.

Troubleshooting

Error Cause Recovery
ProviderAuthError Invalid or expired API token Check token permissions and expiry
delete_threshold exceeded Plan would delete too many rules Review the plan; raise delete_threshold or use --force
HTTP 429 Too Many Requests Provider API rate limit hit Wait and retry; reduce max_workers
Partial zone failure One zone failed, others succeeded Re-run for the failed zone only (--zone <name>)
Checksum mismatch State changed between plan and sync Re-run plan to get a fresh checksum
No rules file for zone Zone configured but YAML file missing Create rules/<zone>.yaml or remove zone from config

Writing a provider

A provider is a Python package that:

  1. Implements BaseProvider — the @runtime_checkable Protocol in octorules.provider.base defining 19 methods + 4 properties.
  2. Declares SUPPORTS — a frozenset[str] of optional features (custom_rulesets, lists, page_shield, zone_discovery).
  3. Registers phases — calls register_phases() at import time with the provider's phase definitions. Each Phase can include a prepare_rule callable for provider-specific rule preparation (expression normalization, default fields, action injection). The core planner calls this hook — it contains no provider-specific logic itself.
  4. Registers a linter plugin — optional; provides provider-specific lint rules. Linters should only check their own phases (not phases owned by other providers).
  5. Declares an entry point — in pyproject.toml:
[project.entry-points."octorules.providers"]
my_provider = "my_package:MyProvider"

Unsupported optional methods must still exist to satisfy the Protocol. The convention: read methods (list_*, get_*, get_all_*) return empty collections; mutation methods (create_*, update_*, put_*, delete_*) raise ProviderError.

Provider utilities

octorules.provider.utils and related modules provide shared helpers so providers don't reinvent common patterns:

  • retry_with_backoff() (octorules.retry) — exponential backoff with jitter for retrying transient API errors.
  • fetch_parallel() (octorules.provider.utils) — concurrent fetching with error propagation and worker capping.
  • to_plain_dict() — convert provider SDK objects to plain dicts.
  • normalize_fields() / denormalize_fields() — bidirectional field name mapping between YAML and provider API formats.
  • validate_path_within() (octorules.pathutil) — path traversal protection for file operations.
  • make_error_wrapper() — decorator factory for mapping provider SDK exceptions to ProviderError/ProviderAuthError.

Extension hooks (plan, apply, format, validate, dump, audit) registered via octorules.extensions are validated at registration time — the framework checks the callable's signature against the expected parameters and raises TypeError immediately if they don't match, so provider authors get clear errors during development rather than at runtime.

CI/CD integration

For GitHub Actions, see octorules-sync — a ready-made action that runs plan on PRs and sync on merge to main.

Development

Local setup

git clone git@github.com:doctena-org/octorules.git
cd octorules
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev,wirefilter]"

Pre-commit hook

ln -sf ../../scripts/hooks/pre-commit .git/hooks/pre-commit

Running tests and linting

pytest
ruff check octorules/ tests/
ruff format --check octorules/ tests/

Releasing a new version

  1. Update the version in pyproject.toml (single source of truth).
  2. Commit and push to main.
  3. Tag the release and push the tag:
git tag -a v0.17.0 -m "v0.17.0"
git push origin v0.17.0

Pushing a v* tag triggers the release workflow, which runs the full lint and test suites before building, publishing to PyPI, and creating a GitHub Release.

License

octorules is licensed under the Apache License 2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

octorules-0.26.1.tar.gz (287.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

octorules-0.26.1-py3-none-any.whl (304.9 kB view details)

Uploaded Python 3

File details

Details for the file octorules-0.26.1.tar.gz.

File metadata

  • Download URL: octorules-0.26.1.tar.gz
  • Upload date:
  • Size: 287.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for octorules-0.26.1.tar.gz
Algorithm Hash digest
SHA256 4e9ee7c16376a379cdc0734e0551124000f5aa7af773ac7895d2e9ea77e44178
MD5 570c4fc10f11efc70ff44c959768069e
BLAKE2b-256 f8ae2e1a0eb085708633c8fb1bea1af33a921545fbcbfa1e65ade0bd1a823242

See more details on using hashes here.

Provenance

The following attestation bundles were made for octorules-0.26.1.tar.gz:

Publisher: release.yaml on doctena-org/octorules

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file octorules-0.26.1-py3-none-any.whl.

File metadata

  • Download URL: octorules-0.26.1-py3-none-any.whl
  • Upload date:
  • Size: 304.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for octorules-0.26.1-py3-none-any.whl
Algorithm Hash digest
SHA256 819e8476a50536d78f50b685990c72ed60c214f5476c5251b0fc28ca2caeeced
MD5 3a3096470468acc3c5c40ebee7c9dbe9
BLAKE2b-256 6a3da26577ceb11d7c794f884bb0ceca382256a45aa43872867987221b97f244

See more details on using hashes here.

Provenance

The following attestation bundles were made for octorules-0.26.1-py3-none-any.whl:

Publisher: release.yaml on doctena-org/octorules

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page