Skip to main content

AppSec Rules Pack

CI Security CI/CD OpenSSF Scorecard OpenSSF Best Practices PyPI Python License: Apache 2.0

A versioned AppSec rules pack and the validator that keeps it honest. Each rule states what to review, what evidence proves it, how to fix it, and how long an exception may last, with mappings to OWASP ASVS 5.0, the OWASP API Security Top 10, OWASP Top 10:2025, CWE, and NIST SSDF. The appsec-rules CLI validates packs against a JSON Schema contract and semantic checks, and checks review records, the per-service outcome of a review, against the pack's own exception policy. A CI gate reads both reports.

Recorded CLI demo: successful baseline validation followed by a missing-title error

The demo is real CLI output. Recreate it from a source checkout with python docs/assets/record-cli-demo.py.

Contents

Scope

It does It does not
Define a JSON Schema contract for AppSec review rules Scan application code or execute rules
Ship a generic baseline of 29 rules, each with a compliant and a violating example Claim compliance: mappings are review aids, not conformance
Validate packs: schema, duplicate IDs, exception windows and policy, mapping formats, rule lifecycle, sensitive values Embed enforcement: CI consumes the JSON report and decides (ADR-0004)
Derive a rule index, a Semgrep metadata scaffold, a SARIF rule catalog, and a mapping coverage report Turn the derived scaffold into detections: its patterns are placeholders (ADR-0001)
Check a review record against the pack: every rule accounted for, evidence for met, exceptions allowed, complete, unexpired, and inside the window Confirm the evidence is true, or decide what may stay open: the record is self-declared and the gate decides (ADR-0006)

The baseline covers authentication including federated sign-in and token issuance, password storage, object- and function-level authorization, a second approver for high-risk actions, input validation, injection (including argument injection) and XSS, unsafe deserialization, SSRF, secrets, file handling, sensitive data in storage and caches, logging and an audit trail for sensitive records, dependency risk, configuration including CORS, session hardening, CSRF, webhook integrity, excessive data exposure, mass assignment, open redirect, rate limiting, and two risks of LLM features: prompt injection, and tools or retrieval that reach beyond the caller's permissions. It contains no product names, tenant identifiers, customer data, secrets, or environment-specific configuration.

Two areas are out of the baseline on purpose. Privacy obligations such as consent and lawful basis depend on the jurisdiction, so they belong in a privacy pack; the baseline keeps only the security side, retention of sensitive data. CI/CD pipelines, infrastructure as code, and container images are a different subject from the application: review them with a policy tool built for them, such as the OSS Security Policy as Code Starter Kit, or with your own pack next to the baseline.

Mapping coverage of the baseline: CWE and NIST SSDF on 29 of 29 rules; ASVS 5.0 on 28 of 29 (ASVS 5.0.0 has no requirement for LLM prompts, so APPSEC-LLM-001 maps to the OWASP Top 10 for LLM Applications instead); the optional OWASP API Top 10 2023 on 23, OWASP Top 10:2025 on 26, and OWASP Top 10 for LLM Applications 2025 on the 2 LLM rules.

Quick start

pip install "appsec-rules-pack==0.9.0"
curl -LO https://github.com/lucashgrifoni/AppSec-Rules-Pack/releases/download/v0.9.0/appsec-baseline.yaml
appsec-rules validate appsec-baseline.yaml --require-examples --fail-on-warnings

Expected output: Validation passed: 1 file, 29 rules, 0 errors, 0 warnings.

Install into a virtual environment (python -m venv .venv, then activate it). In Windows PowerShell, curl is an alias for Invoke-WebRequest; download the baseline with Invoke-WebRequest <url> -OutFile appsec-baseline.yaml instead.

Installation

pip install "appsec-rules-pack==0.9.0"

This installs the appsec-rules console script and requires Python 3.11 or newer. Pin a reviewed version, such as appsec-rules-pack==0.9.0, when the CLI runs as a quality gate.

The distribution contains the validator, the CLI, and the JSON Schema. It does not contain a rules pack. Take the baseline from the assets of the release you pin, copy rules/appsec-baseline.yaml, or write your own pack.

To work from source:

python -m venv .venv
.venv/bin/python -m pip install -e ".[dev]"     # Windows: .venv\Scripts\python

Verify a release

Releases from v0.2.0 on are built and published by publish-pypi.yml through PyPI Trusted Publishing, with no long-lived token. From v0.3.1, each GitHub Release carries the wheel, the sdist, a CycloneDX SBOM (sbom.cdx.json), and the baseline pack, all four with a SLSA build-provenance attestation; from v0.4.0 it also carries the signed provenance bundle (appsec-rules-pack-<tag>.intoto.jsonl). From v0.5.0, a release is published only from a tag on main that matches the package version and passes the tests, and the files are built with hash-pinned tools in a job that cannot publish. v0.1.0 was published by hand and has no attestation. All release tags are signed.

Verify a downloaded asset with a current GitHub CLI:

gh attestation verify appsec-baseline.yaml \
  --repo lucashgrifoni/AppSec-Rules-Pack \
  --signer-workflow lucashgrifoni/AppSec-Rules-Pack/.github/workflows/publish-pypi.yml \
  --source-ref refs/tags/v0.9.0

Or offline, against the bundle attached to the release:

gh attestation verify appsec-baseline.yaml \
  --repo lucashgrifoni/AppSec-Rules-Pack \
  --bundle appsec-rules-pack-v0.9.0.intoto.jsonl

The same commands work for the wheel, the sdist, and sbom.cdx.json. A passing check proves the artifact came from this repository's release workflow at that tag. It does not prove the rule guidance is complete or that an application is secure. Treat a failed or unavailable check as unverified provenance. ADR-0003 explains the release design.

CLI reference

Command Purpose
appsec-rules init <file> Write a starter pack that passes the strict gate; --id and --prefix set the pack id and rule id prefix
appsec-rules validate <file-or-dir>... Validate packs, or every .yaml/.yml pack under directories (linked directories are not entered); duplicate rule ids are checked across all of them
appsec-rules init-review <pack> <record> --subject <text> --reviewer <text> Write a review record that lists every enabled rule, to fill in; --subject-ref, --force
appsec-rules review <pack> <record> Check a review record against a pack; --as-of, --fail-on-warnings, --format json
appsec-rules export index <pack> [-o file] Derive a machine-readable rule index (JSON)
appsec-rules export semgrep <pack> [-o file] Derive a Semgrep scaffold with rule metadata and placeholder patterns
appsec-rules export sarif <pack> [-o file] Derive a SARIF 2.1.0 rule catalog with an empty results array
appsec-rules report coverage <pack> [-f json] [-o file] Count framework mappings per rule and AppSec category
appsec-rules --version Print the installed version

validate options:

Option Effect
--fail-on-warnings Exit non-zero on warnings as well as errors
--require-examples Warn when an enabled rule has no compliant/violating example
--format json Emit a JSON report for CI

The JSON report is identified by "schema": "appsec-rules-validation/v1" and described by validation-report.schema.json. It has a summary object (files, rules, errors, warnings, ok) and a files array of per-file issues. Each issue has a level, a stable code, the rule_id it belongs to (or null), a path inside the pack, the line and column of that path in the file (or null), and a message. The text output shows the same location after the message. Match on code, not on the message text, which may be reworded. Validation exits 0 on a passing pack, 1 on a failing one, and 2 on a usage error such as a missing path. VERSIONING.md lists the codes and what counts as a breaking change.

The export and coverage commands only derive metadata; they do not validate first. Run validate --require-examples --fail-on-warnings as the gate. From a source checkout, python -m appsec_rules_pack works in place of appsec-rules.

Writing a rules pack

A pack is a pack block plus one or more rules. This minimal pack is complete and passes the strict gate. appsec-rules init my-pack.yaml writes it for you; then validate it and grow it. Every field shown is required except schema_version and examples, which the strict gate (--require-examples) expects anyway. The full contract is appsec-rule.schema.json, and the 29 rules in rules/appsec-baseline.yaml are worked examples.

pack:
  id: my-pack
  name: My Rules Pack
  version: 0.1.0
  schema_version: "0.5"
  mode: advisory
  owner: appsec
  description: A minimal rules pack to start from.

rules:
  - id: MYPACK-ERRORS-001
    title: Require controlled error handling
    description: Verify that invalid user input returns controlled errors without stack traces.
    severity: medium
    category: configuration
    status: enabled
    enforcement: advisory
    targets:
      - api
    mappings:
      owasp_asvs:
        - V16.5.1
      cwe:
        - CWE-209
      nist_ssdf:
        - PW.7
    evidence:
      required:
        - Error handling path returns a documented response shape.
      signals:
        - Negative tests assert controlled error messages for invalid input.
    match:
      type: review
      includes:
        - API handlers that process untrusted input.
      excludes:
        - Local developer-only scripts that are not shipped.
    remediation:
      guidance: Replace raw exception output with a controlled error contract.
      validation:
        - Run negative tests for invalid input and malformed requests.
    exceptions:
      allowed: true
      max_days: 30
      required_fields:
        - owner
        - justification
        - expires_at
    examples:
      compliant:
        language: python
        snippet: |
          except ValueError:
              log.exception("order parse failed")
              return {"error": "invalid order"}, 400
        explanation: The client gets a fixed message; the detail stays in server logs.
      violating:
        language: python
        snippet: |
          except ValueError as error:
              return {"error": repr(error)}, 500
        explanation: The raw exception reaches the client and can expose internal details.

Rules files must be UTF-8 YAML without aliases or duplicate keys, and no larger than 10 MiB; the validator rejects anything else before checking the schema.

docs/rule-fields.md lists every field, its limits and allowed values, and how to adapt the baseline: keep it as published next to your own pack, or fork it. Shapes that are easy to get wrong: evidence and match are objects, not lists; remediation needs guidance; required_fields accepts only owner, justification, expires_at, compensating_control, and validation_plan.

Rule ids follow PREFIX-AREA-NNN, so an organisation can use its own prefix (ACME-AUTH-001) next to the baseline's APPSEC- rules. CWE and NIST SSDF mappings are required; ASVS, owasp_top_10_2025, owasp_api_top_10_2023, and owasp_llm_top_10_2025 are optional. Keys starting with x- are allowed on the pack, on each rule, and inside mappings for your own metadata. A pack may declare the schema it targets with pack.schema_version: "0.5".

Rules are advisory by default. Each one carries a stable ID and severity, a target surface and category, framework mappings, expected evidence and review signals, match guidance, remediation and validation steps, exception requirements, a lifecycle status (deprecated rules carry a deprecation block), and a compliant and a violating example. pack.mode and each rule's enforcement (advisory, audit, blocking) are policy metadata for whoever consumes the pack. They never change the exit code of validate, which only judges whether the pack itself is well formed. CONTRIBUTING.md describes the severity model and the topic-based mapping convention.

Recording a review

A pack states a policy. A review record states what one review found against it: for one subject (a service, a repository, a release), each rule is met, not-met, not-applicable, or excepted.

review:
  pack: appsec-baseline
  pack_version: 0.9.0
  subject: payments-api
  subject_ref: v3.2.0
  reviewer: appsec-team
  date: 2026-10-01

results:
  - rule: APPSEC-AUTHZ-001
    status: met
    evidence:
      - tests/test_authz.py::test_other_tenant_payment_returns_404
  - rule: APPSEC-LOG-001
    status: not-met
    notes: Refund failures log the card holder name.
  - rule: APPSEC-RATELIMIT-001
    status: excepted
    exception:
      owner: payments-team
      justification: Per-client quotas need a new store; planned for 3.3.
      expires_at: 2026-12-15
      compensating_control: Gateway limit and an alert on 429 spikes.
appsec-rules review appsec-baseline.yaml payments-api-review.yaml --format json

This record covers three rules, so every other baseline rule comes back unreviewed with a review-missing-result warning, and the command still exits 0. In CI, add --fail-on-warnings so a record with gaps fails the job.

review checks the record against the pack: the record names the right pack, each rule exists and appears once, met cites evidence, and each exception is allowed by the rule, has the fields the rule requires, has not expired, and fits in the rule's max_days. Enabled rules with no result are reported as unreviewed, and a not-met or not-applicable rule without notes gets a review-justification-missing warning. review.subject_ref is optional and names the exact revision reviewed (a commit, tag, image digest, or package version). A not-met or excepted result may set assessed_severity when the finding is more or less severe than the rule: the report then shows it as that rule's effective_severity and counts it in summary.open_by_effective_severity, while severity and summary.open_by_severity keep the rule's value (ADR-0009). Keys starting with x- on a result are copied into that rule's entry in the JSON report.

To start a record, appsec-rules init-review appsec-baseline.yaml review.yaml --subject payments-api --reviewer appsec-team writes one result per enabled rule, each not-met with no notes. Every rule counts as open and warns until it is filled in, so the record cannot pass --fail-on-warnings before the review is done.

For not-met, evidence is optional. Use it to point to the failing code, test, or observation so another reviewer can locate the defect. It does not change the status.

The exit code says whether the record is valid, nothing more. A valid record can still have open rules. The JSON report ("schema": "appsec-rules-review/v1", described by review-report.schema.json) lists every rule with its severity, enforcement, and status, and counts open rules (not-met or unreviewed) by both, so the gate can apply your policy. The record format is review-record.schema.json. examples/review/ has a complete record against the baseline.

All 29 baseline rules have enforcement: advisory. A gate that checks only summary.open_by_enforcement.blocking would allow every open baseline rule. Gate on summary.open_by_severity instead, with the severity threshold your team requires. For example, reject a report when its critical or high count is non-zero, after checking that the command exited 0 and summary.ok is true. The worked review has one open medium rule. To gate by enforcement, fork the pack and raise selected rules to blocking; the field reference explains the field and adapting the baseline explains the fork. Count maps omit zero entries; in Python, read a missing severity with summary["open_by_severity"].get("high", 0).

When you upgrade the baseline, review your records in the same change. A new enabled rule has no result in an existing record, so it shows as unreviewed with a review-missing-result warning, and a record that declares the old pack_version also gets review-pack-version-mismatch. With --fail-on-warnings, both fail the CI job until the record gets a result for each new rule and the new pack_version. The changelog lists added rules under each release.

Use it in CI

  • examples/README.md has a GitHub Actions job that installs a pinned release, downloads and verifies the baseline, validates your packs, checks your review records, and gates on open rules, plus a PowerShell version.
  • examples/validation_gate.py is a stdlib-only gate for any other CI system. It passes only when the CLI exits 0 and the report says ok: true.
  • .github/workflows/policy-gate.yml is this repository's reference gate that consumes the JSON report.

Optional executable Semgrep rules

exports/semgrep-rules/ holds three hand-maintained, tested Semgrep rules, kept separate from the validator (ADR-0005):

Baseline rule Detection
APPSEC-INJECT-001 Flask query/form values reaching SQL arguments on locally created sqlite3 connections and cursors
APPSEC-SSRF-001 Flask query/form values reaching the URL argument of module-level Requests calls
APPSEC-DESER-001 Flask request bodies, files, and query/form values reaching pickle, marshal, or yaml.load without a safe loader

The other 26 baseline rules have no executable detection, and these three cover only their documented source and sink combinations. The layer README lists the known false positives and false negatives. Run the fixture suite with a separately installed engine:

python -m pip install "semgrep==1.179.0"
semgrep --test --strict --metrics=off --config exports/semgrep-rules/rules exports/semgrep-rules/tests

CI runs this on Linux. Use Linux, macOS, or WSL: on native Windows, semgrep --test did not run these fixtures in a check on 2026-10-05.

The appsec-rules CLI never invokes Semgrep, and export semgrep still emits only the metadata scaffold.

Project layout

.github/            CI, security, policy-gate, Scorecard, Pages, and release workflows
docs/adr/           Architecture decision records
docs/assets/        CLI demo and social preview
examples/           Starter pack, worked review, and CI integration examples
exports/            Derived artifacts and the optional executable Semgrep rules
rules/              The baseline rules pack
site/               Source of the project landing page
src/appsec_rules_pack/
                    Validator, review checker, CLI, exporters, schemas, starter pack
tests/              Test suite and pass, fail, and warning fixtures

Documentation

Document Contents
CHANGELOG.md Changes per release
VERSIONING.md Version numbers, compatibility rules, public surface, issue codes
STATUS.md Current state, dated verification results, risks and limits
ROADMAP.md What shipped and what comes next
TECHNICAL_SPEC.md Rule contract and validation design
docs/rule-fields.md Every pack and rule field, and how to adapt the baseline
docs/v1-readiness.md Proposed 1.0 contract freeze, migration policy, and pending acceptance criteria
docs/adr/ Architecture decisions

Contributing, security, and license

Used the pack on your own work? The 1.0 release waits for that kind of evidence: tell us what worked and what did not with the adoption feedback form.

Contributions are welcome. Read CONTRIBUTING.md for rule authoring principles, the severity and exception models, and the required checks:

python -m ruff check .
python -m pytest --cov=appsec_rules_pack --cov-report=term-missing
python -m appsec_rules_pack validate rules --require-examples --fail-on-warnings
python -m build

Participation follows the Code of Conduct. Report vulnerabilities privately as described in SECURITY.md; do not open a public issue.

Licensed under the Apache License 2.0.

Metadata

Release files for appsec-rules-pack 0.9.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for appsec-rules-pack 0.9.0
File Size Uploaded
appsec_rules_pack-0.9.0.tar.gz 53.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for appsec-rules-pack 0.9.0
File Interpreter ABI Platform
appsec_rules_pack-0.9.0-py3-none-any.whl Python 3 none any Details

Total release size: 104.7 kB

Release files / appsec_rules_pack-0.9.0.tar.gz

Download URL appsec_rules_pack-0.9.0.tar.gz
Size 53.8 kB
Tags Source
SHA-256 checksum
How to use checksums
26f8521ae1728f1a919f42aa50c90257d7650b7586f03028a4e8f7a42805ef1e
BLAKE2b-256 checksum
How to use checksums
e1022a2fe82957457dc323a5b04b79a77a5ba9d67f3138324bb3ef9106a68ade
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release files / appsec_rules_pack-0.9.0-py3-none-any.whl

Download URL appsec_rules_pack-0.9.0-py3-none-any.whl
Size 50.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cd9ff4e81c31f095e49c1508a352a3cbe1315ae4328866b5b7632d82db97095c
BLAKE2b-256 checksum
How to use checksums
d708c378fed7f63beaa34b9c5a7068e191527681b26fb4802425e30a26cf09e3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.9.0 This release

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page