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
1 change: 0 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ baseline-mcp/
│ │ └── formatters/ # SARIF output generation
│ │
│ ├── darnit-example/ # Example implementation (docs reference)
│ ├── darnit-plugins/ # Plugin utilities
│ └── darnit-testchecks/ # Test implementation (for testing)
│
├── docs/
Expand Down
63 changes: 63 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,69 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `ALLOWED_MODULE_PREFIXES` on `HandlerRegistry`, `PluginRegistry`, and
`AdapterRegistry`. The allowed packages come from installed
implementations; there is no list to extend (#490).
- **BREAKING:** the check and remediation adapter system, which no audit or
remediation ever dispatched to (#487). `darnit.core.adapters`
(`CheckAdapter`, `RemediationAdapter`, `CommandCheckAdapter`,
`ScriptCheckAdapter`, `AdapterRegistry`) is removed, with the `darnit.core`
re-exports `CheckAdapter`, `RemediationAdapter`, `AdapterInfo`,
`ENTRY_POINT_CHECK_ADAPTERS`, and `ENTRY_POINT_REMEDIATION_ADAPTERS`, and
`AdapterCapability` and the adapter `RemediationResult` in
`darnit.core.models` (`darnit.remediation.RemediationResult` is
unchanged). The `darnit.check_adapters` and `darnit.remediation_adapters`
entry point groups are no longer read. Verification runs through sieve
handlers; an external tool runs through `exec` or a plugin step type.
- **BREAKING:** `darnit.core.registry.PluginRegistry` keeps only framework
discovery (`discover_frameworks`, `list_frameworks`,
`get_framework_info`, `get_framework_path`, `clear_cache`). Removed:
`AdapterInfo`, `discover_all`, `discover_check_adapters`,
`discover_remediation_adapters`, `list_check_adapters`,
`get_check_adapter_info`, `get_check_adapter`, `has_check_adapter`, the
matching remediation-adapter methods, `register_framework`,
`has_framework`, `register_check_adapter`,
`register_remediation_adapter`, `register_from_adapter_config`, and
`get_plugin_summary` (#487).
- **BREAKING:** `darnit.config` no longer exports `AdapterType`,
`CheckConfig`, `OutputMapping`, or `FrameworkDefaults`, and
`darnit.config.framework_schema` drops them with the adapter config models
(`PythonAdapterConfig`, `CommandAdapterConfig`, `ScriptAdapterConfig`,
`HttpAdapterConfig`, `AdapterConfig`), `FrameworkConfig.defaults`,
`FrameworkConfig.adapters`, `get_adapter_config`, `get_check_adapter`, and
`get_remediation_adapter`. `EffectiveControl` loses `check_adapter`,
`check_handler`, `check_config`, and `remediation_adapter`;
`EffectiveConfig` loses `adapters` and `get_adapter`; `merge_control` no
longer takes `defaults`; and `ControlSpec.metadata` no longer carries
`check_adapter` or `remediation_adapter` (#487).
- **BREAKING:** a control-level `check` key in a framework TOML or an
operator custom control now fails loading, like any unknown control key
(#487).
- **BREAKING:** framework TOML `[adapters]` tables and `[defaults]`
`check_adapter` / `remediation_adapter` are no longer read. A framework
that still has them loads (unknown top-level tables are accepted), and
they have no effect. The shipped frameworks drop their `[defaults]`
blocks (#487).
- **BREAKING:** `CheckContext.locator` and the `darnit.locate` package
(`UnifiedLocator` and its `sync_to_project` writer, `FoundEvidence`,
`LocateResult`, `CheckOutput`, and the tool output normalizer) are
removed; no handler read them. The control `locator`
configuration and `use_locator` are unchanged (#487).
- **BREAKING:** `darnit.storage` (`StorageBackend`, `StorageRecord`,
`FileBackend`, `ArchivistaBackend`, `MemoryBackend`, `get_backend`) and the `storage_config` parameter of
`generate_attestation_from_results` are removed. Use the `darnit.stores`
attestation store (`attestation_store`) (#487).
- **BREAKING:** the `adapter=` control filter (`--tags adapter=...`) is
removed; it never matched in `darnit audit`. `adapter` is now an ordinary
tag key (#487).
- **BREAKING:** `darnit plan` no longer prints `[adapter: ...]` after each
control, and `darnit validate` no longer prints an `Adapters:` count
(#487).
- The `darnit-plugins` workspace package (never published) and the
`darnit-testchecks` check adapters, their `darnit.check_adapters`,
`darnit.remediation_adapters`, and `darnit.adapters` entry points, and its
`[adapters.builtin]` table (#487).
- Unused code: `darnit_baseline.remediation.routing`
(`classify_writeback`), the threat-model classes in
`darnit_baseline.threat_model.models` other than `StrideCategory`, and
`darnit.remediation.RemediationResult.to_markdown` (#487).

### Added

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ The MCP server respects these environment variables:

Darnit is designed with security in mind. Key security features include:

- **Module Whitelist**: Dynamic adapter loading is restricted to trusted module prefixes (`darnit.*`, `darnit_baseline.*`, `darnit_plugins.*`, `darnit_testchecks.*`)
- **Module Path Policy**: A configured `module:attribute` handler reference resolves only to `darnit` or a package registered under the `darnit.implementations` entry points
- **Dry-Run Mode**: All remediation actions support dry-run to preview changes before applying
- **Sigstore Attestations**: Cryptographically signed compliance attestations with transparency logging
- **Plugin Verification**: Sigstore-based verification of plugin packages
Expand All @@ -474,7 +474,7 @@ Default trusted publishers: `kusari-oss`, `kusaridev`
- [ ] Use fine-grained GitHub tokens with minimal permissions
- [ ] Always use `dry_run=True` first when remediating
- [ ] Review `.project/` changes (not-applicable claims) in pull requests
- [ ] Name custom adapter packages with `darnit_` prefix
- [ ] Ship custom handler modules in a package registered under `darnit.implementations`
- [ ] Enable plugin verification in production (`allow_unsigned = false`)

For comprehensive security guidance, see [docs/SECURITY_GUIDE.md](docs/SECURITY_GUIDE.md).
Expand Down
14 changes: 7 additions & 7 deletions THREAT_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ No authentication decorator was found on this endpoint. If the endpoint handles
#### TM-T-001: Potential command injection via subprocess.run

**Risk:** HIGH (severity × confidence = 5.40)
**Location:** `packages/darnit/src/darnit/core/adapters.py:231`
**Location:** `packages/darnit/src/darnit/core/adapters.py:231` (removed in 0.2.0, #487)
**Source:** `tree_sitter_structural` — query `python.sink.dangerous_attr`

[subprocess/dynamic] Entire command built dynamically — highest injection risk without taint confirmation. Command argument is populated from configuration/dict lookup within the same function scope. Opengrep taint analysis will lift confirmed cases to high confidence.
Expand Down Expand Up @@ -184,7 +184,7 @@ No authentication decorator was found on this endpoint. If the endpoint handles
#### TM-T-003: Potential command injection via subprocess.run

**Risk:** HIGH (severity × confidence = 5.40)
**Location:** `packages/darnit-plugins/src/darnit_plugins/adapters/kusari.py:253`
**Location:** `packages/darnit-plugins/src/darnit_plugins/adapters/kusari.py:253` (removed in 0.2.0, #487)
**Source:** `tree_sitter_structural` — query `python.sink.dangerous_attr`

[subprocess/dynamic] Entire command built dynamically — highest injection risk without taint confirmation. Command argument is populated from configuration/dict lookup within the same function scope. Opengrep taint analysis will lift confirmed cases to high confidence.
Expand Down Expand Up @@ -354,7 +354,7 @@ No authentication decorator was found on this endpoint. If the endpoint handles
#### TM-T-021: Potential command injection via subprocess.run

**Risk:** MEDIUM (severity × confidence = 2.40)
**Location:** `packages/darnit/src/darnit/core/adapters.py:354`
**Location:** `packages/darnit/src/darnit/core/adapters.py:354` (removed in 0.2.0, #487)
**Source:** `tree_sitter_structural` — query `python.sink.dangerous_attr`

[subprocess/parameterized] Command list contains variable arguments that may originate from external input. Opengrep taint analysis will lift confirmed cases to high confidence.
Expand Down Expand Up @@ -787,7 +787,7 @@ The `local_path` MCP parameter is the primary trust boundary — the user (MCP c

Dynamic imports allow loading arbitrary modules at runtime. If the module name originates from untrusted input, an attacker can achieve arbitrary code execution.

> **Mitigation (verified, #490):** This is the only place darnit-core imports a module named by configuration. `resolve_module_path` serves every caller that turns a `module:attribute` string into an import: MCP tool handlers (`ToolRegistry.load_handler`, `server/registry.py`), handler-registry lookups (`HandlerRegistry.get_handler`), and Python adapter configuration (`PluginRegistry` in `core/registry.py`, `AdapterRegistry` in `core/adapters.py`). Before importing, it requires the form `a.b.c:attr` (identifiers only, no relative or empty parts) and a top-level package that is `darnit` or the package of an implementation discovery loaded from the `darnit.implementations` entry points. The allowed set is derived from installed entry point metadata, not a list in code, so it covers third-party implementations and excludes packages that are not installed implementations. A refused path raises `HandlerImportRefused` naming the path and the allowed packages; at MCP server start the tool is not registered and the refusal is logged at ERROR. Residual risk: an allowed package is code the operator installed, so the policy narrows what a configured string can reach but does not sandbox it. `[mcp.tools]` comes from an installed framework TOML or an operator-supplied `darnit serve <config.toml>`, never from the audited repository (framework-design.md 6.5, 14).
> **Mitigation (verified, #490):** This is the only place darnit-core imports a module named by configuration. `resolve_module_path` serves every caller that turns a `module:attribute` string into an import: MCP tool handlers (`ToolRegistry.load_handler`, `server/registry.py`) and handler-registry lookups (`HandlerRegistry.get_handler`); the Python adapter loaders that also used it were removed in 0.2.0 (#487). Before importing, it requires the form `a.b.c:attr` (identifiers only, no relative or empty parts) and a top-level package that is `darnit` or the package of an implementation discovery loaded from the `darnit.implementations` entry points. The allowed set is derived from installed entry point metadata, not a list in code, so it covers third-party implementations and excludes packages that are not installed implementations. A refused path raises `HandlerImportRefused` naming the path and the allowed packages; at MCP server start the tool is not registered and the refusal is logged at ERROR. Residual risk: an allowed package is code the operator installed, so the policy narrows what a configured string can reach but does not sandbox it. `[mcp.tools]` comes from an installed framework TOML or an operator-supplied `darnit serve <config.toml>`, never from the audited repository (framework-design.md 6.5, 14).

```
414 | module_path, sep, attr = path.rpartition(":")
Expand All @@ -812,9 +812,9 @@ No compound attack paths identified.

### Immediate Actions (Critical / High)

1. **Potential command injection via subprocess.run** — `packages/darnit/src/darnit/core/adapters.py:231` (mitigated: TOML config source, list-form subprocess)
1. **Potential command injection via subprocess.run** — `packages/darnit/src/darnit/core/adapters.py:231` (mitigated: TOML config source, list-form subprocess; file removed in 0.2.0, #487)
2. **Potential command injection via subprocess.run** — `packages/darnit/src/darnit/sieve/builtin_handlers.py:137` (mitigated: TOML control definitions, list-form subprocess)
3. **Potential command injection via subprocess.run** — `packages/darnit-plugins/src/darnit_plugins/adapters/kusari.py:253` (mitigated: hardcoded binary, list-form subprocess)
3. **Potential command injection via subprocess.run** — `packages/darnit-plugins/src/darnit_plugins/adapters/kusari.py:253` (mitigated: hardcoded binary, list-form subprocess; file removed in 0.2.0, #487)
4. **Potential command injection via subprocess.run** — `packages/darnit/src/darnit/server/tools/git_operations.py:382` (mitigated: list-form subprocess, gh CLI validates args)

### Short-term Actions (Medium)
Expand All @@ -825,7 +825,7 @@ No compound attack paths identified.
4. **Potential command injection via subprocess.run** — `packages/darnit/src/darnit/tools/audit_org.py:62` (mitigated: list-form subprocess, gh validates)
5. **Potential command injection via subprocess.run** — `packages/darnit/src/darnit/tools/audit_org.py:113` (mitigated: list-form subprocess, gh validates)
6. **Potential command injection via subprocess.run** — `packages/darnit/src/darnit/core/utils.py:27` (mitigated: list-form subprocess, gh validates)
7. **Potential command injection via subprocess.run** — `packages/darnit/src/darnit/core/adapters.py:354` (mitigated: trusted TOML config)
7. **Potential command injection via subprocess.run** — `packages/darnit/src/darnit/core/adapters.py:354` (mitigated: trusted TOML config; file removed in 0.2.0, #487)
8. **Potential command injection via subprocess.run** — `packages/darnit/src/darnit/server/tools/test_repository.py:141` (test tool only)
9. **Potential command injection via subprocess.run** — `packages/darnit/src/darnit/server/tools/git_operations.py:45` (mitigated: list-form subprocess)
10. **Potential command injection via subprocess.run** — `packages/darnit/src/darnit/server/tools/git_operations.py:53` (mitigated: list-form subprocess)
Expand Down
Loading
Loading