Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,9 @@ htmlcov/
Thumbs.db

# Local configuration (keep examples, ignore actual)
.baseline.toml
project.toml
!example.*.toml
!example.*.yaml
# Test fixtures with tracked .baseline.toml. The top-level rule above
# would otherwise strip them and CI would either deselect the whole
# suite (parity) or auto-pick a different framework than the fixture
# expects (harness).
!tests/darnit/harness/fixtures/**/.baseline.toml
!tests/darnit/parity/fixtures/**/.baseline.toml

# Logs
*.log
Expand Down
22 changes: 11 additions & 11 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ Here's the end-to-end flow that an AI assistant (or human) goes through when aud
Load existing project DETERMINISTIC things actually live
context from .project/ │ file exists? API call? (e.g., "my security
▼ docs are at
Merge user overrides PATTERN docs/security.txt,
from .baseline.toml │ regex match? heuristics? not SECURITY.md")
Apply operator config PATTERN docs/security.txt,
(outside the repository) │ regex match? heuristics? not SECURITY.md")
▼
LLM b) Framework needs user
│ ask calling AI to judge to confirm values it
Expand Down Expand Up @@ -423,19 +423,19 @@ Three configuration layers, merged at runtime:
│ Defines: controls, passes, templates, context prompts│
│ Owner: implementation author │
├──────────────────────────────────────────────────────┤
│ Layer 2: .baseline.toml (user overrides) │
│ Defines: disabled controls, severity overrides, │
│ custom tags, plugin trust settings │
│ Owner: project maintainer │
│ Layer 2: Operator configuration (outside the repo) │
│ Defines: pass overrides, custom controls, plugins, │
│ MCP servers, stores, trust, policy │
│ Owner: whoever runs darnit │
├──────────────────────────────────────────────────────┤
│ Layer 3: .project/project.yaml (project context) │
│ Layer 3: .project/ (project context and claims) │
│ Defines: maintainers, CI provider, governance model, │
│ security contacts, release info │
│ security contacts, not-applicable claims │
│ Owner: project; darnit writes only confirmed values │
└──────────────────────────────────────────────────────┘
```

At audit time, the framework merges Layer 1 + Layer 2 into an "effective config", then injects Layer 3 into each `CheckContext.project_context`.
At audit time, the framework merges Layer 1 + Layer 2 into an "effective config", then injects Layer 3 into each `CheckContext.project_context`. Nothing in the audited repository changes Layers 1 and 2; a repository's `.baseline.toml` is not read (an audit reports one notice pointing at `darnit config migrate`).

### Context Collection

Expand All @@ -457,7 +457,7 @@ AI Assistant
▼
audit_openssf_baseline(level=1)
│
├─► Load framework TOML + .baseline.toml → EffectiveConfig
├─► Load framework TOML + operator configuration → EffectiveConfig
├─► Load .project/project.yaml → project_context
├─► Convert controls → ControlSpec + Pass objects
├─► Filter by level (and optionally by tags)
Expand Down Expand Up @@ -501,7 +501,7 @@ For detailed mermaid diagrams of audit internals, remediation flow, context life
| **Config** | `config/loader.py` | Load and parse TOML framework configs |
| | `config/framework_schema.py` | Schema for framework TOML validation |
| | `config/control_loader.py` | Convert TOML controls → `ControlSpec` objects |
| | `config/merger.py` | Merge framework + user configs → effective config |
| | `config/merger.py` | Merge framework + operator configuration → effective config |
| **Context** | `context/dot_project.py` | Load/save `.project/project.yaml` |
| | `context/dot_project_mapper.py` | Map TOML context keys to project YAML paths |
| | `context/sieve.py` | Context sieve (progressive auto-detection) |
Expand Down
53 changes: 40 additions & 13 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Removed

- `.baseline.toml` reading and the code that existed only for it:
`load_user_config` (including its `trusted` path),
`load_user_config_with_report`, `validate_user_config`, `deep_merge`, and
`BASELINE_TOML_DEPRECATION_ACTIVE` (`darnit.config.merger`);
`darnit.config.user_schema` (`UserConfig`, `UserSettings`,
`ControlOverride`, `ControlGroup`, `CustomControl`, `ControlStatus`,
`create_user_config`, `create_user_config_with_kusari`) and their
`darnit.config` re-exports (`UserControlOverride`, `UserControlStatus`);
and `load_effective_audit_config`, `get_excluded_control_ids`, and
`get_adapter_for_control` (`darnit.tools.audit`). Custom controls and pass
overrides belong in operator configuration.
- The root `example.baseline.toml`.
- Python helpers that read or wrote raw context values: `load_context`,
`load_stored_context`, `flatten_user_context`, `get_context_value`,
`get_raw_value`, `is_context_confirmed`, `save_context_value`, and
Expand All @@ -49,6 +61,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
inside the audited repository is refused, and a file writable by others is
refused under `--strict-operator-config` (on by default in recognized CI).
Reports record its source and digest.
- `darnit run -f/--framework NAME` selects the framework, as `audit` and
`harness` do (#507). An unknown name exits 1 instead of reporting a clean
run over no controls; without the option, `run` audits `openssf-baseline`,
the same default as `audit`.
- `darnit config show` (resolved operator configuration, digest, permission
check, and redacted settings), `darnit config trust add|list|remove`
(edits `[trust].repos`), and `darnit config migrate [REPO] [--force]`
Expand Down Expand Up @@ -260,12 +276,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
network in CI now FAILs RE-02.01. The Nix signal counts only when RE-01.02
passed, and RE-01.02 no longer concludes PASS on its own, so the fetch is
no longer outweighed.
- Per-control `status` and `reason` in a repository's `.baseline.toml`, ignored
since 0.1.1, are read during the deprecation release as not-applicable
claims under the trust rules: a claim counts only for a repository the
operator trusts, or after the operator confirms it; otherwise the control
is evaluated and counts as non-compliant. `darnit config migrate` moves
them to `.project/darnit.yaml`.
- **BREAKING:** `PENDING_LLM` is removed; `PENDING` with
`pending.kind = "llm_judgment"` replaces it in every output, including MCP
tool results and JSON reports.
Expand Down Expand Up @@ -386,13 +396,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
replaces an invalid `.project/project.yaml` with a scaffold: when either
`.project/` file is present but invalid, writes are refused with the
validation errors, and audit reports list them as warnings.
- `.baseline.toml` is deprecated. In this release darnit reads its
per-control `status`/`reason` as not-applicable claims under the same rules
as `.project/` claims, still honors `extends` naming a registered framework,
ignores its tool settings (see Security above), and warns once per setting
in the file with the setting's new home. A later release will ignore the
file with a notice. `darnit init` no longer creates `.baseline.toml`; it
explains `.project/` claims and operator configuration.
- **BREAKING:** darnit no longer reads a repository's `.baseline.toml`.
Nothing in it has any effect: not per-control `status`/`reason` (even for
a repository the operator trusts), not `extends` (use `--framework`), and
not `version` or `settings`, which 0.1.1 still honored. When the file is
present, an audit logs one WARNING and adds the same notice to the report's
`warnings`, pointing at `darnit config migrate`, which moves its claims to
`.project/darnit.yaml` and prints an operator configuration fragment for
its other settings. Its keys are no longer listed in
`ignored_repository_settings`. `darnit init` no longer creates
`.baseline.toml`; it explains `.project/` claims and operator
configuration.
- **BREAKING:** Python API changes from removing `.baseline.toml`:
`merge_configs(framework, operator=None)` and
`merge_control(control_id, framework_control, defaults)` take no user
configuration; `load_effective_config`, `load_effective_config_by_name`,
`load_controls_from_toml`, and `load_controls_by_name` take no repository
path, and `load_effective_config_auto(framework_path=None,
framework_name=None, *, operator=None)` no longer takes one;
`EffectiveControl` loses `status`, `status_reason`, `from_user`, and
`is_applicable()`, and `EffectiveConfig` loses `cache_results`,
`cache_ttl`, `timeout`, and `get_excluded_controls()`;
`run_checks`/`run_sieve_audit` `apply_user_config` is renamed
`evaluate_claims` (it still turns `.project/` claim evaluation on or off).
No MCP tool or CLI parameter served only `.baseline.toml`.
- A not-applicable claim makes a control `N/A` (excluded from the level's
denominator) only when it is honored: the repository is trusted, an
explicit claim gives a reason, and no declared evidence contradicts it, or
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ Context values are read only through `resolve_context` (`darnit.config.context_r

Project claims live in `.project/darnit.yaml` (`controls.<id>: {status, reason, asserted_by}`). A not-applicable claim, explicit or implied by a `.project/` context value, counts only when its outcome is `honored` (trusted repository and uncontradicted, or operator-confirmed); `pending` counts as non-compliant and `contradicted` has no effect (feature 040).

Tool configuration comes only from operator configuration (`--operator-config PATH`, else `$XDG_CONFIG_HOME/darnit/config.toml` / `~/.config/darnit/config.toml`, else built-in defaults), never from the audited repository. It holds plugins, MCP servers, pass overrides, custom controls, stores, LLM settings, `[trust].repos`, CI trust rules, and policy. `darnit config show|trust|migrate` inspect it, edit the trust list, and move a deprecated `.baseline.toml` (during the deprecation release only per-control `status`/`reason`, read as claims, and `extends` by registered name are honored, with a warning per setting; switch `BASELINE_TOML_DEPRECATION_ACTIVE` in `config/merger.py`).
Tool configuration comes only from operator configuration (`--operator-config PATH`, else `$XDG_CONFIG_HOME/darnit/config.toml` / `~/.config/darnit/config.toml`, else built-in defaults), never from the audited repository. It holds plugins, MCP servers, pass overrides, custom controls, stores, LLM settings, `[trust].repos`, CI trust rules, and policy. `darnit config show|trust|migrate` inspect it, edit the trust list, and move a legacy `.baseline.toml` (claims to `.project/darnit.yaml`, a printed operator configuration fragment for the rest). darnit never reads a repository's `.baseline.toml` otherwise; an audit of a repository that has one reports a single notice pointing at `darnit config migrate`.

Remediation plans, then applies only what it planned (feature 043; framework-design.md 4, 15). Handlers get `HandlerContext.mode` (`plan`/`apply`), return `FileChange`s in `evidence["file_changes"]`, and never write; the executor is the single writer, skips files with uncommitted user changes (`user_changes_present`, in the preview too), and records every write in an operator-side run manifest. A handler without `supports_plan=True` is not previewable. Platform settings change only through `platform_setting` and the platform engine (also behind `enable_branch_protection`): it reads first (unreadable means no write), plans the minimal change, never weakens an existing setting, writes nothing when already satisfied or met by a ruleset, uses the default branch, and reads back. `api_call`, `requires_confirmation`, `dry_run_supported`, and `dry_run_command` are removed; an exec remediation never touches the platform. The `[remediation]` policy (`platform`, `high_impact`: `prompt` default, `manual`, `auto`) comes from operator configuration only. Approval is by digest (`approve`), bound to the observed state; `dry_run=False` alone approves nothing, a batch never covers a high-impact change, and `safe = false` or non-previewable items need their own digest under every policy. Git tools take `run_id`, commit only manifest files with a `Darnit-Remediation-Run` trailer, never stash, and refuse unsafe repository states. Outcomes (`fixed`, `changed_not_passing`, `changed_not_verified`, `unchanged`, `needs_approval`, `needs_confirmation`, `manual`, `error`) come from a cache-neutral re-check, and summaries and commit/PR gates read outcomes, never report text. Every entry point previews by default; `darnit run` writes only with `--apply`.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ Darnit is designed with security in mind. Key security features include:

### Plugin Security

Configure trusted publishers in `.baseline.toml`:
Configure trusted publishers in operator configuration (`~/.config/darnit/config.toml`, or the file named by `--operator-config`):

```toml
[plugins]
Expand All @@ -473,7 +473,7 @@ Default trusted publishers: `kusari-oss`, `kusaridev`

- [ ] Use fine-grained GitHub tokens with minimal permissions
- [ ] Always use `dry_run=True` first when remediating
- [ ] Review `.baseline.toml` changes in pull requests
- [ ] Review `.project/` changes (not-applicable claims) in pull requests
- [ ] Name custom adapter packages with `darnit_` prefix
- [ ] Enable plugin verification in production (`allow_unsigned = false`)

Expand Down
8 changes: 4 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This document tracks future enhancements and design work needed.

**Status**: Design needed
**Priority**: Medium
**Context**: Currently, configuration is per-repository via `.baseline.toml`. We need a more flexible system.
**Context**: Tool configuration is one per-user operator configuration file (feature 040); a repository's `.baseline.toml` is no longer read. Organization-level policy is tracked in #503. We need a more flexible system.

### Problem Statement

Expand All @@ -20,7 +20,7 @@ Users need the ability to:

1. **Enterprise Central Policy**
```toml
# .baseline.toml
# operator configuration
extends = [
"https://config.company.com/darnit/security-policy.toml",
"https://config.company.com/darnit/team-backend.toml",
Expand All @@ -30,7 +30,7 @@ Users need the ability to:

2. **Monorepo Shared Config**
```toml
# packages/my-service/.baseline.toml
# operator configuration for packages/my-service
extends = [
"../../.darnit-shared.toml",
"openssf-baseline",
Expand All @@ -41,7 +41,7 @@ Users need the ability to:
```
org-policy.toml (remote server)
└── team-policy.toml (remote server)
└── .baseline.toml (local repo)
└── operator configuration (local)
```

### Design Considerations
Expand Down
14 changes: 7 additions & 7 deletions docs/DECISION_FLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ Only after the person accepts does the agent fill in the digest; the confirmatio
│
▼
┌───────────────────────────────────┐
│ Check .baseline.toml for │
│ Check framework TOML for │
│ control-specific adapter config │
└───────────────────────────────────┘
│
Expand Down Expand Up @@ -1467,16 +1467,16 @@ Use the provided verification script instead.
│ Configuration Files │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ project.toml │ .baseline.toml │
│ project.toml │ operator configuration (user-level) │
│ ───────────────────────────────────────────────────────────────────────── │
│ Purpose: Project metadata & │ Purpose: MCP server configuration │
│ documentation locations │ & adapter settings │
│ Purpose: Project metadata & │ Purpose: tool settings, never read │
│ documentation locations │ from the audited repository │
│ │ │
│ Contains: │ Contains: │
│ • schema_version │ • schema_version │
│ • [project] name, type, controls │ • [settings] defaults, timeouts │
│ • [security] policy, threat_model │ • [adapters.*] custom adapter configs │
│ • [governance] contributing, etc. │ • [controls.*] per-control overrides │
│ • [project] name, type, controls │ • [plugins], [mcp_servers], [stores] │
│ • [security] policy, threat_model │ • [custom_controls.*] │
│ • [governance] contributing, etc. │ • [controls.*] pass overrides │
│ • [testing] docs, requirements │ │
│ • [releases] verification │ │
│ • [ci.github] workflows, etc. │ │
Expand Down
12 changes: 7 additions & 5 deletions docs/MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ expr = 'output.json.status == "pass"'
## Plugin Security Configuration

### New Configuration
Add to your `.baseline.toml`:
Add to your operator configuration (`~/.config/darnit/config.toml`, or the file named by `--operator-config`):

```toml
schema_version = 1

[plugins]
# Require signed plugins in production
allow_unsigned = false
Expand All @@ -64,12 +66,12 @@ allow_unsigned = false
trusted_publishers = [
"https://github.com/my-org",
]

# Per-plugin configuration
[plugins."my-plugin"]
version = ">=1.0.0"
```

## Repository `.baseline.toml`

darnit no longer reads a repository's `.baseline.toml`; an audit of a repository that still has one reports a single notice. Run `darnit config migrate [REPO]` to move its per-control `status`/`reason` claims to `.project/darnit.yaml` and print a proposed operator configuration fragment for its other settings. Review both, add the fragment to your operator configuration, select the framework with `--framework` instead of `extends`, then delete `.baseline.toml`.

## Context System

### Project Context
Expand Down
Loading
Loading