Skip to content
cirsecPublic

About

Open, free and versioned security configuration baselines. Written from primary sources, mapped to CIS, ISO 27001, NIST, CISA SCuBA and ANSSI.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

66 Commits

Folders and files

Repository files navigation

CIRSEC Baselines

Open, free and versioned security configuration baselines, maintained by the CIRSEC community.

Published at https://baselines.cirsec.org.

CIRSEC Baselines describe how to securely configure systems and services (cloud tenants, operating systems, network devices, software). Each recommendation, called a control, is written from scratch by domain experts from primary sources (vendor documentation, RFCs, public-domain guides), and released under a permissive license so anyone can use it, including in commercial products.

When a control covers the same ground as another framework (CIS Benchmarks, ISO/IEC 27001, NIST SP 800-53, CISA SCuBA, ANSSI...), it links to it through a typed mapping. We reference other frameworks, we never copy them.

Contents


Principles

  1. Clean room. Controls are written from primary sources only. Contributors must not have a proprietary benchmark open while writing a control. Mappings to other frameworks are added afterwards, in a separate pass, ideally by another contributor.
  2. Provenance. Every control lists the sources it was written from (references). Every commit is signed off (DCO).
  3. Stable identifiers. A control ID is never renumbered and never reused.
  4. Reference, don't reproduce. For frameworks with restrictive licenses, only identifiers appear in this repository (reference_policy in baselines.yml).
  5. Customizable. Tunable values are parameters with per-profile defaults, so organizations adapt a baseline without forking it.
  6. Stable format. The schemas are designed to be complete from day one. Growth happens through content, not format changes. Tool-specific data goes in x- fields.
  7. Intent, not code. A control declares the expected state through declarative engines (sysctl, sshd-config, graph-api...). Checks and fixes for every output format (shell, Ansible, PowerShell...) are generated by the tooling. Handwritten code is the exception.
  8. Open by default. Control text is CC BY 4.0. Code (scripts, generated artifacts) and tooling are Apache 2.0.

Repository layout

baselines/
├── baselines.yml                    # global settings: licenses, locales, ID format, engines, frameworks
├── LICENSE                          # Apache 2.0 (tooling)
├── LICENSE-CONTENT                  # CC BY 4.0 (control content)
├── schema/                          # JSON Schemas (draft 2020-12)
│   ├── baselines.schema.json
│   ├── category.schema.json
│   ├── standard.schema.json
│   ├── control.schema.json
│   ├── control-i18n.schema.json
│   ├── engines/<engine>.schema.json # spec of each engine
│   ├── tailoring.schema.json        # organization overrides, read by scanners and generators
│   └── result.schema.json           # scan output contract
├── templates/                       # copy-paste starting points, valid against the schemas
│   ├── control.yml
│   ├── control-i18n.yml
│   └── tailoring.yml
├── SECURITY.md                      # how to report a dangerous fix or a false negative
├── tools/                           # validator, generators, release builder, tests
│   ├── validate.py
│   ├── tailor.py                    # checks a tailoring file against the content
│   ├── generate.py                  # audit/remediation scripts and Ansible role for one profile
│   ├── oscal.py                     # OSCAL catalog and profiles
│   ├── build.py
│   ├── common.py
│   ├── lib/cirsec.sh                # shell runtime inlined into every generated control script
│   ├── requirements.txt
│   └── tests/                       # with fixtures/ (test-only standard) and golden/ (expected outputs)
├── .github/
│   ├── CODEOWNERS                   # reviewers per standard
│   ├── ISSUE_TEMPLATE/              # new control, wrong check result, broken fix
│   └── workflows/
│       ├── ci.yml                   # validate, test, lint scripts, build, DCO, on every push and PR
│       └── release.yml              # manual release of one standard (workflow_dispatch)
└── categories/
    ├── cloud/                       # category
    │   ├── _category.yml
    │   └── m365/                    # standard
    │       ├── _standard.yml
    │       ├── controls/            # one file per control, in the default locale (en)
    │       │   └── CB-M365-AUTH-001.yml
    │       └── i18n/
    │           └── fr/              # translations, one file per control
    │               └── CB-M365-AUTH-001.yml
    └── os/
        ├── _category.yml
        └── debian/
            ├── _standard.yml
            ├── controls/
            ├── scripts/             # (optional) handwritten fix scripts, one folder per control
            │   └── CB-DEB-AUTH-007/
            │       └── fix.sh
            └── i18n/fr/
Level Location File Schema
Root / baselines.yml baselines.schema.json
Category categories/<category>/ _category.yml category.schema.json
Standard categories/<category>/<standard>/ _standard.yml standard.schema.json
Control .../<standard>/controls/ <ID>.yml control.schema.json
Translation .../<standard>/i18n/<locale>/ <ID>.yml control-i18n.schema.json
Script .../<standard>/scripts/<ID>/ any name linted per engine, Apache 2.0

Design decisions

  • The folder tree is the source of truth. No hand-maintained index lists categories, standards or controls. Indexes and the website are generated from the tree, so they cannot drift.
  • One file per control. A standard can hold hundreds of controls. One file each keeps reviews readable, avoids merge conflicts between experts working in parallel, and gives a clean git log / git blame per control, which is what proves provenance.
  • One standard per product, not per product version. debian covers Debian 12 and 13. Version differences are handled per control (applicability.versions, audit.checks[].versions). This avoids duplicating hundreds of controls at each release. A new major product version is just a new entry in applies_to.
  • _ prefix for metadata files. They sort first and are clearly distinct from content.
  • Small metadata is translated inline, controls in separate files. Categories and standards carry an i18n block. Controls are large and translated by different people, so each translation is its own file with its own history.

Demo category

categories/demo/buffet is a deliberately silly standard (all-you-can-eat buffet security) that uses every field of the format, with comments explaining each one. Read it before writing your first control. It relies on demo-only entries in baselines.yml (demo-sensor engine, demo-food-code and demo-buffet-threats frameworks) and is excluded from releases and from the public site.

Control Shows
CB-BUF-HYG-001 Full example: automated checks per version, fixes generated from checks (from_check) with effects, parameters with profile defaults, setting, mappings, threats, verification per version, French translation
CB-BUF-GUARD-001 Manual control, applicability block with versions, facts and requires, requires relation
CB-BUF-HYG-002 Partial assessment, enum and boolean parameters, logic: any, check limited by a fact, handwritten fix in a script file, hardened-only profile, review status
CB-BUF-ABUSE-001 Deprecated control with its replacement
CB-BUF-ABUSE-002 Replacement (supersedes), conflicts relation, x- extension fields

Naming rules

  • Category and standard folder names are lowercase slugs (cloud, m365, debian) equal to the id field of their metadata file. They appear in URLs and never change once published.
  • A control file is named exactly <ID>.yml, in controls/ and in each i18n/<locale>/.

Control identifiers

CB-<STANDARD>-<DOMAIN>-<NNN>
CB-M365-AUTH-001
Segment Meaning Defined in
CB CIRSEC Baselines fixed
STANDARD Standard code (uppercase, unique, never changes) _standard.yml → code
DOMAIN Functional area at creation time (AUTH, NET, MAIL...) _standard.yml → domains
NNN Sequential number inside the domain, 3 digits assigned on creation

The domain is frozen in the ID. A control is never renumbered, never moved to another domain, and its number is never reused. If a control must change meaning or domain, it is deprecated and replaced by a new one (see Lifecycle).


Standards, profiles and domains

A standard (_standard.yml) declares:

Field Purpose
id, code URL slug and ID code. Both permanent.
status draft, active or deprecated.
version SemVer of the current or upcoming release. released holds its date.
applies_to Products, vendors and supported versions.
profiles Named sets of controls (e.g. baseline, hardened). extends chains them.
domains Allowed DOMAIN codes for control IDs.
facts Machine-detectable conditions (package installed, desktop environment...). See Facts.
maintainers People responsible for the standard.
primary_sources Sources the standard is written from. Never a proprietary benchmark.
i18n Translated title, description, domain and profile names.

Profiles replace the usual "Level 1 / Level 2". A control lists the profiles it belongs to. With hardened: extends: baseline, applying hardened applies every baseline control too, plus the hardened ones, and uses hardened parameter defaults. Standards can define other profiles (e.g. workstation, small-business) without any format change.

A profile extends at most one parent. A control shared by two branches (e.g. server-hardened and desktop-hardened) lists both profiles, and both in profile_defaults when its values differ.


Control format

The full contract is schema/control.schema.json. The annotated starting point is templates/control.yml.

Group Field Req. Purpose
Identity id yes Permanent ID, equal to the file name.
title yes The recommendation, as the desired state, in our own words.
finding yes How a failure appears in a report: title (the problem) and optional description.
status yes draft, review, active, deprecated.
profiles yes Profile keys from the standard.
risk yes severity (info to critical) and optional score (0-10).
assessment yes automated, partial or manual. automated requires at least one check.
tags Free lowercase slugs.
Scope applicability versions, facts (machine-detectable), requires (human-only: licenses, editions), conditions. Omitted = always applies.
Content description yes What the setting or practice is. Markdown.
rationale yes Why it matters. Markdown.
impact yes Side effects of applying it. "None known." if none.
notes Edge cases, exceptions, vendor quirks.
setting location, default_value, recommended_value for single-setting controls.
parameters Tunable values. See below.
Audit audit.manual yes Human check procedure.
audit.logic all (default) or any of the checks must pass.
audit.checks[] Machine checks: id, engine, description, versions, facts, spec validated by the engine schema.
Remediation remediation.manual yes Human fix procedure.
remediation.effects Restart, services, disruption, lockout risk. Required with scripts.
remediation.rollback How to revert, in prose.
remediation.scripts[] Automated fixes: from_check, or engine + content / file. See Remediation.
Links references[] yes Primary sources used, with kind and accessed date.
mappings[] Links to control frameworks.
threats[] ATT&CK techniques, CWE, CVE mitigated.
related_controls[] requires, related or conflicts with other CIRSEC controls.
People authors yes Writers.
reviewers Experts who reviewed.
verified[] Tests against real environments: date, by, versions, environment, notes. Required on every applicable version before an automated or partial control becomes active.
Lifecycle introduced_in yes Standard version where it first appeared.
created, updated yes Dates. updated is bumped on every content change.
deprecated_in, deprecation_reason, superseded_by Required when status: deprecated.
supersedes On the replacing control.

Title vs finding

title is the rule, finding.title is what a report shows when the check fails:

title: Block user consent to third-party applications
finding:
  title: Users can grant third-party apps access to company data

Checks, engines and facts

Engines

engine must be a key declared under engines in baselines.yml. Each engine declares:

  • capabilities:
    • check: evaluates a declarative spec.
    • fix: can generate the remediation from a check spec (scripts[].from_check).
    • script: accepts handwritten code (scripts[].content or file).
  • spec_schema: the JSON Schema in schema/engines/ that every spec of this engine must match.
Engine Capabilities For
sysctl, package, systemd-unit, file-perms, sshd-config, dconf check, fix Linux
windows-registry check, fix Windows
graph-api check, fix Microsoft 365
exchange-powershell, osquery, file-content, postmortem check read-only sources
shell, powershell check, script escape hatch

Prefer declarative engines. A control says what (key: net.ipv4.ip_forward, value: 0), and the engine's generators produce the check and the fix for every output format. A bug is fixed once in the generator, for every control. A new output format (Terraform, Intune...) is a new generator, without touching any control. Script engines are for what no declarative engine covers.

Engines describe semantics, not implementations: a scanner may evaluate sysctl by reading /proc/sys, through osquery, or any other way. Adding an engine is a change to baselines.yml plus one schema in schema/engines/.

Script engine checks exit with 0 pass, 1 fail, 2 not applicable, anything else error. Stdout is the evidence.

Scanners report results per audit.checks[].id, so a multi-check control can say exactly which part failed.

Facts

applicability.conditions and applicability.requires are text for humans. To let a scanner decide on its own that a control does not apply, a standard declares facts: conditions with a detection check.

# _standard.yml
facts:
  - key: openssh-server
    title: OpenSSH server is installed
    detect:
      engine: package
      spec: { name: openssh-server, state: installed }
  - key: desktop-gnome
    title: GNOME desktop is installed
    detect:
      engine: package
      spec: { name: gnome-shell, state: installed }

Facts are referenced by key, and all listed facts must be true:

  • applicability.facts: the whole control. When a fact is false, the control is not_applicable.
  • audit.checks[].facts and remediation.scripts[].facts: one check or script. This is how one control "lock the screen" holds a dconf check for desktop-gnome and another check for desktop-kde.

requires stays for what no tool can detect (licenses, contracts).


Remediation

remediation:
  manual: |
    Human procedure, always required.
  effects:                      # required when scripts are present
    restart: service            # none | service | session | reboot
    services: [ssh]             # required when restart is service
    disruption: low             # none | low | high
    lockout_risk: true          # can cut off admin access: disabled by default in generated artifacts
  rollback: |
    Human procedure to revert.
  scripts:
    - id: dropin
      from_check: sshd-effective        # 1. generated from a check (engine with the fix capability)
    - id: inline
      engine: shell                     # 2. short handwritten code (engine with the script capability)
      content: |
        some-command --value "$CB_MAX_DAYS"
    - id: long
      engine: shell                     # 3. longer code in <standard>/scripts/<ID>/fix.sh
      file: fix.sh
  • from_check first. The fix is generated from the check's spec, versions and facts.
  • Handwritten code receives parameters as environment variables CB_<NAME> (max_days → CB_MAX_DAYS), never through {{ }}. Textual substitution into code is a quoting and injection trap. The validator rejects {{ }} in code.
  • Script files live in <standard>/scripts/<control ID>/. Every file must be referenced, so it can be linted, tested and reviewed like code.
  • Fixes are idempotent and back up what they change. Automated rollback is not promised, rollback is prose.
  • effects lets generators restart each service once at the end, and keep fixes with lockout_risk off unless the tailoring file opts in.
  • assessment only describes the audit. A control can be automated to check and have no automated fix (e.g. a Microsoft 365 setting without a write API).

Parameters and customization

Values an organization may want to tune are declared as parameters, and referenced as {{ name }} in text (setting.recommended_value, description, audit.manual...) and in declarative check specs. Handwritten code reads them from CB_<NAME> environment variables instead.

setting:
  recommended_value: "At most {{ max_days }} days"
parameters:
  - name: max_days
    type: integer          # string | integer | number | boolean | enum | list
    description: Maximum allowed lifetime.
    unit: days
    default: 180           # used by every profile without its own value
    profile_defaults:
      hardened: 90
    minimum: 1
    maximum: 365

Resolution order for a value:

  1. the override in the user's tailoring file;
  2. profile_defaults of the profile, then of each profile it extends, nearest first (desktop-hardened → desktop → core);
  3. default.

minimum, maximum and allowed bound what users may set. allowed is required for type: enum.

In a spec, a value that is exactly one placeholder (value: "{{ min_temp_c }}") is replaced by the parameter with its type (integer, boolean, list). A placeholder inside a longer string is replaced as text.

bundle.json holds the resolved values of every control for every profile (resolved_parameters).


Tailoring and scan results

Tailoring (schema/tailoring.schema.json, example in templates/tailoring.yml): how an organization adapts a released standard without forking it. It picks a profile, overrides parameters within their bounds, excludes controls with a reason and an optional review date, and opts in to fixes with lockout_risk. The file lives in the organization's repository.

Scan results (schema/result.schema.json): what any scanner outputs, so any dashboard can read it.

Control status Meaning
pass / fail Checks evaluated with audit.logic
not_applicable A version or fact does not match
error A check could not run
manual assessment: manual, a human must check
review assessment: partial and every check passed, a human must confirm the rest
excluded Removed by the tailoring file

Each check result carries evidence (observed, expected, source), and the result names the standard version, profile and resolved parameters, so it can be reproduced.

Check a tailoring file against the content (controls exist, parameters within bounds, past review dates):

python tools/tailor.py acme-servers.yml

Generated artifacts

tools/generate.py turns the controls of one profile into code. Every release contains them for each profile, built with the profile defaults. Regenerate with your tailoring file to apply your overrides and exclusions.

python tools/generate.py --standard debian --tailoring acme-servers.yml --out gen/
Output Use
controls/<ID>.sh One self-contained POSIX sh script per control. check prints one line per check and exits 0 pass, 1 fail, 2 not applicable, 3 error, 4 manual or review. fix is a dry run, fix --apply applies.
audit.sh Runs every check, prints a result JSON on stdout and a readable report on stderr: summary and score, failures by severity with observed and expected values, errors, what to check by hand (audit.manual), next commands. On a terminal the JSON goes to ./cirsec-result-<host>-<date>.json instead. Colors on a terminal only, never with NO_COLOR. --verbose lists every entry. Exit 1 if anything fails.
remediate.sh Runs every enabled fix in dependency order (related_controls: requires), dry run unless --apply. Backs up changed files under /var/backups/cirsec/<date>/, restarts each service from effects once at the end, says when a reboot or new login is needed.
ansible/ site.yml and a role running the same control scripts: one task pair (check, fix) per control, tagged with the control ID, cirsec_<standard>_<id>_enabled per control, handlers from effects, --check mode supported.

Rules applied by every generator:

  • Only active controls (--include-drafts adds draft and review for testing).
  • A fix runs only when its check fails, and the check runs again afterwards. A check that cannot run (error) blocks its fix.
  • Fixes with lockout_risk are disabled unless the tailoring file opts in (fixes:), or CB_ENABLE_<ID>=1 for remediate.sh.
  • The product version comes from /etc/os-release (VERSION_ID major), or CB_PRODUCT_VERSION. When it is unknown, version-specific checks report error, never not_applicable.
  • Engines without a shell generator (graph-api, windows-registry, demo-sensor...) report error in shell outputs. They need a scanner that implements them.
  • A fix can only be generated for checks using ==, <= or >= (the fix sets the expected value).

Generators live in tools/generate.py, one check and one fix function per engine. A bug is fixed there once, for every control. tools/tests/golden/ holds the expected output for the test standard in tools/tests/fixtures/lab. After an intended change, refresh it with UPDATE_GOLDEN=1 python -m pytest -q tools/tests/test_generate.py and review the diff.


Mappings and threats

Mappings

A mapping says how our control relates to a control in another framework. Only frameworks of kind: control-framework can be used.

mappings:
  - framework: cis-m365
    framework_version: "7.0.0"
    id: "x.y.z"
    relation: partial
    note: Ours also covers service principals.
relation Meaning
equivalent Same intent and same expected configuration
stricter Ours requires more than theirs
looser Ours requires less than theirs
partial Overlap, each covers things the other does not
related Same topic, different approach

Threats

threats:
  - framework: mitre-attack
    id: T1528
  - framework: cwe
    id: CWE-284

Only frameworks of kind threat-taxonomy, weakness-taxonomy or vulnerability-database can be used here.

Never CIS alone

references (what we wrote from) never contains a proprietary benchmark. mappings may, but never alone: a control whose mappings all point to id-only frameworks gets a warning, so map an open one (ANSSI, NIST, CISA) too. Frameworks carry a display_order so the site lists open public frameworks first and CIS last.

A benchmark published per product version is declared once per version (cis-debian-12, cis-debian-13), since numbering differs between them. framework_version is then the benchmark's own version.

Framework registry

Every framework is declared once in baselines.yml with:

  • kind: which field may use it.
  • id_pattern: regex every referenced identifier must match (catches typos).
  • display_order: sort key on the site, lower first.
  • reference_policy:
    • id-only: only identifiers may appear in this repository. No titles, no excerpts. Applies to CIS Benchmarks and ISO/IEC standards.
    • id-title: identifiers and official titles are allowed (NIST, CISA, ANSSI, MITRE...).

Adding a framework is a change to baselines.yml, not to the schemas.


Translations

  • The default locale (en) lives in controls/. Available locales are listed in baselines.yml.
  • A translation is i18n/<locale>/<ID>.yml, containing only text fields (title, finding, description, rationale, impact, notes, conditions, setting text, parameter descriptions, manual audit and remediation). IDs, checks, parameter values and mappings always come from the source control.
  • Missing fields fall back to the source.
  • source_updated records the source control's updated date at translation time. When the source is updated later, the translation is reported as stale.
  • Categories and standards are translated inline through their i18n block.

Lifecycle and versioning

Control status

draft ──► review ──► active ──► deprecated
  • draft: being written.
  • review: a second expert is reviewing it.
  • active: part of a release.
  • deprecated: kept forever for history, never deleted. Requires deprecated_in, deprecation_reason and superseded_by ([] if retired without replacement). The replacing control lists the old one in supersedes.

Standard releases

  • Each standard has its own SemVer version in _standard.yml.
  • A release is a git tag <standard-id>/v<version>, e.g. m365/v1.0.0. No version folders: history lives in git.
  • The website and the resolver read tags to serve any past version, e.g. https://baselines.cirsec.org/m365/v1.0.0/CB-M365-AUTH-001.
Change Bump
Control deprecated, profile membership changed, parameter default made stricter major
New control, new domain, new profile, new supported product version minor
A fix becomes more disruptive (effects gets worse) minor
Wording, references, mappings, translations, fixes to checks and scripts (same expected state) patch

Extensions

Every object that may need tool-specific data accepts fields prefixed with x- (root, categories, standards, facts, controls, checks, scripts, translations, tailoring, results). They are ignored by the official tooling and never needed to understand a control.

x-mlab:
  rule_id: m365-consent-001

Anything that proves generally useful through x- fields can be promoted to the core format in a future schema_version.


Validation rules

JSON Schemas cover the structure of each file. tools/validate.py also enforces cross-file rules:

  1. _category.yml and _standard.yml id equal their folder name.
  2. Standard code is unique across the repository.
  3. Control id equals its file name, starts with CB-<standard code>-, and its DOMAIN is declared in the standard.
  4. Control IDs are unique across the repository.
  5. profiles exist in the standard. Profile extends chains have no cycles.
  6. applicability.versions and checks[].versions are within the standard's applies_to versions.
  7. Every engine is declared in baselines.yml with a spec_schema that exists. Each check spec (and each fact detect.spec) matches it. The engine has the check capability for checks, fix for the check targeted by from_check, and script for content / file.
  8. mappings[].framework has kind control-framework. threats[].framework has another kind. Every id matches the framework's id_pattern.
  9. {{ name }} placeholders reference declared parameters. Parameter defaults match their type and bounds.
  10. related_controls, supersedes and superseded_by point to existing controls.
  11. Translations: file name, id and folder locale match an existing control and an available locale. Translations with source_updated older than the source's updated are reported as stale (warning).
  12. updated is not earlier than created.
  13. Lifecycle: introduced_in and deprecated_in are not after the standard version, deprecated_in is after introduced_in, deprecation fields only appear on deprecated controls, a control can only supersedes a deprecated one.
  14. Layout: no stray files or folders, .yml extension only, YAML files without duplicate keys.
  15. Facts: unique keys per standard. applicability.facts, checks[].facts and scripts[].facts reference declared facts.
  16. Remediation: effects is present when there are scripts. from_check names a check of the same control. file exists under scripts/<ID>/. Every file in scripts/ belongs to a control and is referenced. No {{ }} in code.
  17. Verification: verified[].versions are standard versions. An active control that is not manual is verified on every version it applies to.

Warnings (do not fail CI unless --strict): stale translations, one-sided supersedes/superseded_by, dates in the future, mappings only to id-only frameworks.


Editor support

Every YAML file starts with a yaml-language-server comment pointing to its schema. With the YAML extension (VS Code, Neovim, Zed...), you get autocompletion, inline documentation and validation while typing.

# yaml-language-server: $schema=../../../../schema/control.schema.json

Tooling, CI and releases

Requires Python 3.11+.

pip install -r tools/requirements-dev.txt

python tools/validate.py              # check everything (add --strict to fail on warnings)
python -m pytest -q tools/tests       # test the tooling itself
python tools/build.py --standard buffet --allow-demo --no-tag-check   # local test build into dist/
python tools/generate.py --standard buffet --profile hardened --include-drafts --out gen/   # generated scripts
python tools/tailor.py templates/tailoring.yml   # check a tailoring file

Continuous integration

.github/workflows/ci.yml runs on every push and pull request:

  1. validate.py: schemas and cross-file rules. Errors are shown as annotations on the PR files.
  2. pytest: every validation rule is tested by breaking it on purpose in a copy of the repository. Generated scripts are run end to end, compared to golden files, and checked with shellcheck.
  3. Script files: shellcheck for .sh, PSScriptAnalyzer for .ps1.
  4. build.py --all: builds every standard at its current version. The archives are attached to the run as a preview-bundles artifact for 7 days.
  5. On pull requests: every commit carries a Signed-off-by matching its author (DCO).

Release content

build.py produces, for one standard:

File Content
cirsec-baselines-<standard>-<version>.zip Controls with status active or deprecated (never draft or review), their translations and script files, the category and standard metadata, baselines.yml, the schemas, LICENSE and LICENSE-CONTENT, plus generated: bundle.json (everything in one JSON: resolved parameters per profile, script files inlined), index.json (lightweight list), oscal/ (OSCAL 1.1.2 catalog and one profile per profile), generated/<profile>/ (generated artifacts)
...zip.sha256 SHA-256 of the archive
RELEASE_NOTES.md Summary (counts per status, profile, severity, translation coverage) and changes since the previous tag of the same standard: added, deprecated, updated, removed

Archives are reproducible: building the same commit twice gives the same checksum.

Releasing a standard

  1. Open a pull request that bumps version (and optionally released) in the standard's _standard.yml, and sets introduced_in / deprecated_in on the controls it touches. Merge it.
  2. Run Actions → Release → Run workflow on the default branch, with:
    • standard: the folder name, e.g. m365
    • version: the version you just set, e.g. 1.0.0
    • prerelease: optional
  3. The workflow validates, refuses if the version does not match _standard.yml or if the tag already exists, builds, then creates the tag <standard>/v<version> and the GitHub release with the archive, the checksum and the notes.

The demo category can never be released.


Contributing

  1. Write from primary sources. Vendor docs, RFCs, man pages, public-domain guides (NIST, CISA, ANSSI). List them in references.
  2. Never copy text from proprietary benchmarks, and do not keep one open while writing. Mappings are added in a separate pass.
  3. Sign off your commits (git commit -s). By signing off you certify, under the Developer Certificate of Origin, that your contribution is your own work and contains no text you have no right to publish. CI refuses pull requests with unsigned commits.
  4. Start from templates/, one control per file, validated against the schemas.
  5. New controls start as draft, move to review once a second expert reads them, and to active when they ship in a release, after being verified on every version they apply to.
  6. Prefer a declarative engine over a script. If none fits, consider adding the engine (schema in schema/engines/, generator in tools/generate.py) rather than writing the same script twice.
  7. Dangerous fix or false negative? Report it privately, see SECURITY.md.

Roadmap

  • Repository layout and global settings
  • JSON Schemas (root, category, standard, control, translation)
  • Templates
  • Validator (schemas and cross-file rules)
  • CI on every push and pull request
  • Release builder and manual release workflow (bundle.json, index.json, notes)
  • Format v2: declarative engines, facts, effects, tailoring, scan results
  • Generators: audit and remediation scripts, Ansible role, OSCAL export
  • Test matrix on real VMs per standard and version (check, fix, check, fix again)
  • PowerShell and Graph generators (Windows, Microsoft 365)
  • Read-only scanner implementing every check engine
  • First standard: Microsoft 365
  • Website and versioned resolver on baselines.cirsec.org

License

  • Control text (categories/**, templates/**): CC BY 4.0
  • Code inside controls (remediation.scripts[].content, files under scripts/, specs of script engines) and every artifact generated from controls (playbooks, scripts): Apache 2.0
  • Schemas and tooling: Apache 2.0

By signing off a contribution (DCO), you license its text under CC BY 4.0 and its code under Apache 2.0.

CIRSEC Baselines is an independent project. It is not affiliated with, endorsed by, or certified by the Center for Internet Security, ISO, NIST, CISA, ANSSI, MITRE or any vendor. Third-party framework identifiers are used for reference only.

About

Open, free and versioned security configuration baselines. Written from primary sources, mapped to CIS, ISO 27001, NIST, CISA SCuBA and ANSSI.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages