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
87 changes: 87 additions & 0 deletions openspec/changes/deepsec-security-scan/design.md
Original file line number Diff line number Diff line change
@@ -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: <fixed safe text> }`; 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.
25 changes: 25 additions & 0 deletions openspec/changes/deepsec-security-scan/proposal.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions openspec/changes/deepsec-security-scan/tasks.md
Original file line number Diff line number Diff line change
@@ -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.
Loading