diff --git a/openspec/changes/deepsec-security-scan/design.md b/openspec/changes/deepsec-security-scan/design.md new file mode 100644 index 0000000..5c5c0d6 --- /dev/null +++ b/openspec/changes/deepsec-security-scan/design.md @@ -0,0 +1,87 @@ +# Design: DeepSec security-scan adapter boundary + +## Decision + +Implement the smallest tracer bullet as a new `examples/security-scan` Hydra consumer. It declares `run_security_scan` in `api/operations.yaml`; Hydra generates CLI, HTTP, and MCP surfaces from that definition. The consumer owns one typed dispatch function and an explicit `SecurityScanner` adapter trait. A local deterministic fixture adapter proves the projection and dispatch seam without requiring DeepSec, a network, a token, or arbitrary command execution. + +A real DeepSec adapter is intentionally outside this first slice. It will be constructed by the consumer from explicitly documented environment variables and passed into application state. It is not a Hydra-core dependency and is not chosen from the operation name. + +## Operation contract + +`run_security_scan` is an explicit POST operation at `/security/scans` with `read: false` and the explicit allowlist `surfaces: [cli, http, mcp]`. + +The normative declaration is: + +```yaml +- name: run_security_scan + description: Run the configured security scanner against an allowed target. + method: POST + path: /security/scans + read: false + output_type: SecurityScanResult + surfaces: [cli, http, mcp] + parameters: + - name: target + description: Closed target identifier accepted by this tracer bullet. + type: string + required: true + location: body + - name: profile + description: Explicit scanner profile; omission selects baseline. + type: string + required: false + location: body +``` + +Inputs: + +- `target`: required body string. The tracer bullet accepts exactly `fixture:demo-repo`; it rejects empty values, values longer than 1024 bytes, control characters, and every other identifier. It is never interpreted as a shell command, filesystem path, or URL. +- `profile`: optional body string. Omission selects `baseline`; `baseline` is the only accepted value. Empty values and every other profile are rejected. + +Output: + +- `SecurityScanResult` is a consumer-owned public serialization model, not a Hydra-generated response schema: `{ "status": "completed", "target": "fixture:demo-repo", "profile": "baseline", "summary": { "critical": 0, "high": 0, "medium": 0, "low": 0 }, "findings": [] }`. Finding summaries, when later added, are ordered deterministically by `(severity, rule_id)` and contain only `severity`, `rule_id`, and a bounded safe `message`. Raw source, credentials, tokens, endpoints, headers, and scanner transport errors are not public fields. + +## Adapter boundary + +```text +CLI / HTTP / MCP + │ generated by Hydra + ▼ +execute_operation(operation, input, state) + │ explicit operation match + ▼ +SecurityScanner::scan(ScanRequest) + │ consumer-selected adapter + ▼ +DeepSec client or deterministic fixture adapter +``` + +The dispatch function matches the declared `run_security_scan` operation explicitly. Any unrecognized operation returns a consumer error. The adapter receives a typed request, never raw generated JSON, preserving a single validation boundary. + +## Configuration and secret handling + +- The fixture adapter is the default for the checked-in example and requires no configuration. +- A future real adapter supports only `DEEPSEC_ENDPOINT` (required absolute HTTPS URL) and `DEEPSEC_TOKEN` (required non-empty credential). Neither value nor a default belongs in source, generated artifacts, tests, docs commands, or output. Missing or invalid values produce `scanner_unavailable` before scanning. +- Adapter errors are private internal values. Before every CLI, HTTP, MCP, or log boundary, the consumer maps them to an allowlisted public error `{ code: "invalid_request" | "scanner_unavailable" | "scan_failed", message: }`; raw adapter errors/configuration are never serialized or logged. Tests inject sentinel token/header/endpoint strings and assert they are absent from every public response and captured log output. + +## Determinism and validation + +- `api/operations.yaml`, `hydra.yaml`, and generated artifacts are committed. +- `hydra-codegen write` followed by `hydra-codegen check` must be byte-identical. +- Tests assert that the operation is projected with an explicit body schema and consistent CLI/HTTP/MCP names. +- Tests assert the fixture adapter is called through the single dispatch function and that invalid target/profile input is rejected by the consumer before adapter invocation. + +## Alternatives rejected + +### Add a `security_scan` primitive to hydra-core + +Rejected: scanning is consumer business logic, not a general projection concern. A core primitive would create scanner-specific coupling without a reusable validation law. + +### Use a raw-request operation + +Rejected: the scanner request is typed JSON, not a signature-verified wire-format integration. Normal generated extraction provides the desired contract. + +### Execute a configured command directly from generated CLI input + +Rejected: it turns a generated operation into an arbitrary command-execution path and makes security posture depend on undocumented quoting behavior. diff --git a/openspec/changes/deepsec-security-scan/proposal.md b/openspec/changes/deepsec-security-scan/proposal.md new file mode 100644 index 0000000..d85d071 --- /dev/null +++ b/openspec/changes/deepsec-security-scan/proposal.md @@ -0,0 +1,25 @@ +# DeepSec security-scan adapter boundary + +## Why + +Hydra can project an explicitly declared operation onto CLI, HTTP, and MCP, but it has no reference for an agent-powered security scan. Consumers need a small, safe pattern for exposing a scanner without teaching Hydra scanner-specific semantics or inferring behavior from an operation name. + +## What changes + +- Define a provider-neutral security-scan tracer bullet as a Hydra consumer example. +- Declare a `run_security_scan` operation explicitly, including its input/output contract and surface locations. +- Define a narrow adapter boundary: generated surfaces decode and validate the declared request, then one consumer-owned dispatch implementation invokes the configured scanner adapter. +- Define environment-only configuration and redacted result rules for scanner credentials and findings. +- Add a runnable example, generated artifacts, deterministic codegen coverage, and documentation after this spec is approved. + +## What does not change + +- Hydra core will not embed DeepSec, vendor a scanner SDK, manage credentials, execute arbitrary shell commands, or infer a scanner from an operation name. +- This change does not create a general plugin system or a production remote-scanner protocol. +- The operation remains a consumer example/tracer bullet; a scanner is selected by explicit consumer configuration. + +## Impact + +- New OpenSpec change: `deepsec-security-scan`. +- Planned implementation areas: `examples/security-scan/`, workspace membership, generated artifacts, README documentation, and focused validation/codegen tests. +- Consumers gain an auditable template for a security-scanning operation whose three surfaces share one dispatch implementation. diff --git a/openspec/changes/deepsec-security-scan/specs/security-scan-adapter.md b/openspec/changes/deepsec-security-scan/specs/security-scan-adapter.md new file mode 100644 index 0000000..ed042cc --- /dev/null +++ b/openspec/changes/deepsec-security-scan/specs/security-scan-adapter.md @@ -0,0 +1,51 @@ +# Security-scan adapter boundary requirements + +## Requirement: Explicit security-scan operation declaration + +A consumer SHALL declare its security-scan operation in `api/operations.yaml`, including method, path, read/write semantics, parameter locations, output type, and the explicit `surfaces: [cli, http, mcp]` allowlist. + +#### Scenario: Generated surfaces share one declared operation + +- **WHEN** the consumer generates artifacts from the security-scan definition +- **THEN** CLI, HTTP, and MCP expose the declared operation without name-based inference +- **AND** all surfaces route to the same consumer dispatch implementation + +## Requirement: Consumer-owned scanner adapter + +The consumer SHALL own scanner selection and invocation behind a typed adapter boundary. Hydra core SHALL NOT depend on a scanner SDK, select a scanner from an operation name, or execute caller-provided shell commands. The tracer bullet SHALL accept exactly `target = "fixture:demo-repo"` and `profile = "baseline"` (with omitted profile defaulting to `baseline`); all other target/profile values, empty values, control characters, and targets longer than 1024 bytes SHALL be rejected before adapter invocation. + +#### Scenario: Fixture scan dispatch + +- **WHEN** a valid tracer-bullet request reaches the generated surface +- **THEN** the consumer dispatch invokes its configured `SecurityScanner` adapter with a typed request +- **AND** the result is returned through the generated surface contract + +## Requirement: Explicit secret configuration and redaction + +A real scanner adapter SHALL accept configuration only through `DEEPSEC_ENDPOINT` (required absolute HTTPS URL) and `DEEPSEC_TOKEN` (required non-empty credential). Source, committed examples, generated artifacts, test fixtures, and user-visible results SHALL NOT contain their values. Adapters SHALL return private internal errors; the consumer SHALL map these to an allowlisted public error code (`invalid_request`, `scanner_unavailable`, or `scan_failed`) with fixed safe text before every CLI, HTTP, MCP, and logging boundary. + +#### Scenario: Scanner credentials are absent from an operation result + +- **WHEN** an adapter reports a scanner error or finding +- **THEN** the response and logs contain only allowlisted public fields or fixed safe error text +- **AND** never contain authorization headers, token values, endpoint values, raw configuration values, or raw adapter errors + +## Requirement: Invalid consumer input does not invoke the scanner + +The consumer SHALL validate its target/profile policy before calling the scanner adapter. + +#### Scenario: Invalid target rejected before dispatch + +- **WHEN** a request supplies an invalid tracer-bullet target or profile +- **THEN** the consumer returns a validation error +- **AND** the configured scanner adapter is not invoked + +## Requirement: Generated artifacts remain deterministic + +The security-scan example SHALL commit generated artifacts and pass its codegen freshness check. Its deterministic fixture result SHALL be exactly `status=completed`, `target=fixture:demo-repo`, `profile=baseline`, summary counts all zero, and an empty findings list. Future findings SHALL sort by `(severity, rule_id)` and expose only bounded safe summary fields. + +#### Scenario: Repeated generation is stable + +- **WHEN** the generator writes artifacts twice from unchanged security-scan inputs +- **THEN** the resulting committed artifacts are byte-identical +- **AND** `hydra check` succeeds diff --git a/openspec/changes/deepsec-security-scan/tasks.md b/openspec/changes/deepsec-security-scan/tasks.md new file mode 100644 index 0000000..8b06a2f --- /dev/null +++ b/openspec/changes/deepsec-security-scan/tasks.md @@ -0,0 +1,14 @@ +# Tasks + +## Spec review + +- [ ] 1. Review and merge this OpenSpec-only PR before implementation. + +## Implementation (blocked by spec review) + +- [ ] 2. Add `examples/security-scan` as a workspace consumer with explicit `run_security_scan` operation and `hydra.yaml`. +- [ ] 3. Implement typed request/result models, the `SecurityScanner` adapter trait, and a deterministic fixture adapter behind one dispatch function. +- [ ] 4. Generate and commit CLI, HTTP, and MCP artifacts; add deterministic `hydra check` coverage. +- [ ] 5. Add dispatch and validation tests proving invalid inputs never reach the adapter and output excludes secret-bearing fields. +- [ ] 6. Document the adapter boundary, explicit environment-only DeepSec configuration, and the fact that Hydra never executes caller-provided commands. +- [ ] 7. Run the full Hydra gate: build, tests, clippy, fmt, and examples/notes codegen check; also run the security-scan codegen check.