diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 00000000..bb3a2105 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,123 @@ +# AppGuardrail Architecture + +**Status:** Accepted as-built/target architecture with maturity labels +**Last reviewed:** 2026-08-12 + +## Architectural goal + +AppGuardrail converts application/security evidence into deterministic findings, reviewable remediation, continuous policy gates, and longitudinal assurance without conflating optional external scanners, issue metadata, or historical coordination with executable detection truth. + +## Component view + +```mermaid +flowchart LR + TARGET[Untrusted target repository/app] + DISC[Discovery/normalization] + BUILTIN[Built-in detector engine] + EXT[Optional external engines] + FIND[Normalized findings] + GATE[Deploy gate] + FIX[Safe fix / fix-pack] + SARIF[SARIF / reports / SBOM] + CP[Control plane] + DASH[Dashboard / buyer evidence] + ISSUE[Issue-to-detection audit] + + TARGET --> DISC + DISC --> BUILTIN + TARGET --> EXT + BUILTIN --> FIND + EXT --> FIND + FIND --> GATE + FIND --> FIX + FIND --> SARIF + FIND --> CP + CP --> DASH + ISSUE --> BUILTIN + ISSUE --> EXT +``` + +## Detector authority + +The detector that observes evidence is authoritative for its finding. `scanner/rules/*.yml` is not automatically executable in full: supported `pattern-regex` entries can be evaluated by the lightweight engine, while Semgrep-style structural `pattern:` fixtures remain non-executable by the built-in matcher unless explicitly routed to a working structural engine. + +External engines retain their own engine/rule/version provenance. AppGuardrail normalizes their output but does not claim their analysis was performed internally. + +## Issue-to-detection boundary + +```mermaid +flowchart LR + HIST[Independent issue/claim inventory] + REG[Detection obligation registry] + ADAPT[Detector-family adapter] + DET[Actual detector] + EV[Closed evidence fixture or authenticated workflow result] + RES[pass/fail/inconclusive obligation result] + + HIST --> REG + REG --> ADAPT + EV --> ADAPT + ADAPT --> DET + DET --> RES +``` + +A registry maps requirement identity to executable detector family; it cannot assert the detector answer. PR #911 is active-PR implementation of this contract. + +## SSRF architecture + +```mermaid +flowchart LR + INPUT[User-controlled URL] + VALID[Destination validation] + STORE[(Stored webhook/callback config)] + EXEC[Outbound executor] + DNS[DNS/IP/redirect checks] + NET[Network request] + + INPUT --> VALID + VALID --> STORE + STORE --> EXEC + EXEC --> DNS + DNS --> NET +``` + +Stored SSRF prevention and scanner detection are separate controls. The control-plane write boundary was hardened through PR #924, while PR #910 added the packaged built-in rule `python-stored-ssrf-webhook-url`; both are implemented on protected `develop`. The detector is intentionally bounded to Python `set_webhook` direct and one-hop persistence flows covered by its regression corpus and does not claim universal interprocedural SSRF detection. + +## Control-plane boundary + +Current standalone control plane is stdlib HTTP + SQLite, with tenant API-key roles and scan/history/drift/webhook configuration. Persistent organization identity is resolved from authenticated key context, not untrusted payload strings. Enterprise replacement of SQLite is behind stable repository service functions and requires migrations/authz/recovery evidence. + +## Remediation authority + +Autofix can perform only narrowly proven semantics-preserving transformations. Other fixes are guidance for a user/agent and become accepted only after rescanning/reverification. Model-generated remediation is never a substitute for scanner evidence. + +## Automation authority + +```mermaid +flowchart LR + DEV[Autonomous developer] + VERIFY[Tests/security exact-head evidence] + REVIEW[Independent review agents/humans] + MERGE[Protected merge] + RELEASE[Release environment] + + DEV --> VERIFY + VERIFY --> REVIEW + REVIEW --> MERGE + MERGE --> RELEASE +``` + +The development model does not own qualifying approval, protected merge, release, or reviewer credentials. Scheduler blocks are RCA inputs; one blocked PR does not idle unrelated safe work. + +## Deployment modes + +1. **CLI/library:** one-shot local scan/report/SBOM/fix. +2. **CI monitor:** installed GitHub workflow generating findings/SARIF and optional control-plane push. +3. **Control plane:** standalone multi-tenant scan history/drift/dashboard/webhook service. +4. **Organization evidence:** read-only aggregation of repository/PR/action evidence for acquisition/security diligence. + +These modes share normalized contracts but can operate separately. + +## Change control + +A new detector engine, persistent schema, tenant authority, arbitrary autofix class, outbound target policy, issue-audit semantics, or automation credential boundary requires ADR and synchronized technical/security/test documentation. \ No newline at end of file diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md new file mode 100644 index 00000000..3f9bb5c4 --- /dev/null +++ b/DOCUMENTATION.md @@ -0,0 +1,33 @@ +# AppGuardrail Documentation Map + +AppGuardrail has extensive rule, scanner, report, release, issue, and scheduler documentation. This index establishes the cross-cutting product and architecture graph so buyers and maintainers do not have to reconstruct the product from README, workflows, issue bodies, and feature-specific notes. + +| Area | Canonical document | +|---|---| +| Product requirements | [`docs/PRD.md`](docs/PRD.md) | +| Technical requirements | [`docs/TRD.md`](docs/TRD.md) | +| Architecture | [`ARCHITECTURE.md`](ARCHITECTURE.md) | +| UML/runtime/detection flows | [`docs/UML.md`](docs/UML.md) | +| Logical/physical data model | [`docs/ERD.md`](docs/ERD.md) | +| Threat model | [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md) | +| Test and detector-validation strategy | [`docs/TEST_STRATEGY.md`](docs/TEST_STRATEGY.md) | +| Operability/recovery/release | [`docs/OPERABILITY.md`](docs/OPERABILITY.md) | +| Detection/issue/evidence traceability | [`docs/TRACEABILITY.md`](docs/TRACEABILITY.md) | +| Architecture decisions | [`docs/adr/README.md`](docs/adr/README.md) | +| Security reporting | [`SECURITY.md`](SECURITY.md) | +| Release automation | [`docs/release-automation.md`](docs/release-automation.md) | +| Productization roadmap | [`docs/product/2026-07-02-2b-krw-sale-readiness-plan.md`](docs/product/2026-07-02-2b-krw-sale-readiness-plan.md) | +| Agent development rules | [`AGENTS.md`](AGENTS.md) | +| Agent context | [`CLAUDE.md`](CLAUDE.md) | +| Product overview | [`README.md`](README.md) | +| Change history | [`CHANGELOG.md`](CHANGELOG.md) | + +## Maturity vocabulary + +- **implemented-main** — source and tests exist on protected `develop`. +- **active-PR** — implementation/evidence exists only on an open pull request. +- **planned** — accepted product target without executable detector/control yet. +- **external-engine** — capability delegated to an optional scanner such as Semgrep/Trivy/Bandit/ZAP rather than AppGuardrail's lightweight built-in matcher. +- **evidence-only** — information visible in reports/history but not yet executable as an AppGuardrail detector. + +Critical current distinction: PR #911's no-exclusions issue-to-detector registry and executable obligation coverage remain **active-PR**, not protected-branch behavior. Stored-webhook SSRF prevention from PR #924 and bounded built-in detection from PR #910 are **implemented-main** as separate controls; the packaged rule covers its tested Python `set_webhook` persistence patterns and is not a universal SSRF taint-analysis claim. \ No newline at end of file diff --git a/docs/ERD.md b/docs/ERD.md new file mode 100644 index 00000000..08c293bf --- /dev/null +++ b/docs/ERD.md @@ -0,0 +1,234 @@ +# AppGuardrail Logical and Persistence ERD + +**Status:** Accepted cross-cutting data model; active-PR detector-obligation entities are labelled. +**Last reviewed:** 2026-08-09 + +Current control-plane persistence is SQLite behind repository service functions. The scanner itself is primarily filesystem/in-memory and emits normalized finding envelopes. This ERD distinguishes current persistent scan history from active-PR/planned detection-obligation evidence. Exact physical SQLite table/column names remain source/migration authority; logical evidence fields below define the product contract even when the current store embeds them in a normalized JSON envelope. + +## Current control-plane model + +```mermaid +erDiagram + ORGANIZATION_RECORD ||--o{ API_KEY_RECORD : authorizes + ORGANIZATION_RECORD ||--o{ SCAN_RECORD : owns + SCAN_RECORD ||--o{ FINDING_RECORD : contains + ORGANIZATION_RECORD ||--o| WEBHOOK_CONFIG : configures + SCAN_RECORD ||--o{ DRIFT_RECORD : compares + + ORGANIZATION_RECORD { + string organization_id PK + string organization_name + datetime created_at + } + + API_KEY_RECORD { + string api_key_id PK + string organization_id FK + string key_digest + string role_code + datetime created_at + datetime revoked_at + } + + SCAN_RECORD { + string scan_id PK + string organization_id FK + string repository_name + string commit_sha + integer blocking_finding_count + string schema_version + datetime created_at + } + + FINDING_RECORD { + string finding_id PK + string scan_id FK + string rule_id + string engine_code + string engine_version + string source_kind_code + string producer_capability_code + string producer_identity + string severity_code + string category_code + string file_path + integer line_number + string signed_payload_digest + string signature_status_code + string signature_algorithm_code + string signature_value + string bounded_metadata_json + } + + DRIFT_RECORD { + string drift_record_id PK + string scan_id FK + string previous_scan_id + integer new_blocker_count + string calculation_version + } + + WEBHOOK_CONFIG { + string webhook_config_id PK + string organization_id FK + string normalized_destination + string destination_policy_version + string delivery_semantics_code + datetime updated_at + } +``` + +Persistent object naming should converge on descriptive two-or-more-word `snake_case` when schema changes occur. + +### Finding evidence provenance contract + +`bounded_metadata_json` is supplementary metadata, not the provenance/authentication authority. A normalized finding that participates in trusted evidence records the following logical keys explicitly: + +- `engine_code` and `engine_version` — exact detector/adapter identity and version; +- `source_kind_code` — built-in source scan, external engine import, workflow evidence, historical/imported evidence, or another closed source class; +- `producer_capability_code` and `producer_identity` — which component/identity was authorized to create the evidence class; +- `signed_payload_digest` — canonical digest over the immutable normalized evidence envelope, including rule, engine/version, source, producer, location/fingerprint, and bounded evidence metadata; +- `signature_status_code`, `signature_algorithm_code`, and `signature_value` — explicit authentication result and signature material where cryptographic authentication is required. + +A local built-in detector may use a closed `not_applicable_local` signature status when provenance is established inside the same verified process boundary; imported/workflow evidence cannot be promoted to authenticated evidence unless its required producer capability, digest coverage, and signature/attestation validation succeed. Missing, malformed, unsupported, mismatched, or unverifiable signature evidence produces `evidence_untrusted`, never `completed_clean` or a registry PASS. + +## Webhook delivery model + +The current generic webhook implementation is **at-most-once per local scan event**: it performs one best-effort POST after destination validation and does not schedule automatic retries. There is therefore no receiver deduplication or synthetic delivery identifier that may be relied on for retry safety today. `delivery_semantics_code` documents this logical contract as `at_most_once_current`. + +A future retrying webhook design must be a reviewed contract change that adds a stable `delivery_id`, persists delivery-attempt state, requires receiver-side deduplication on that identifier, revalidates the destination at every attempt/redirect, and caps retry/backoff. Until that exists, transport failure is retained as bounded evidence and is not retried automatically by the current webhook path. + +Destination validation is also a connection-time control, including for the current one-shot sender. Every send attempt and redirect hop must resolve and evaluate the destination under the current network policy, reject every private, loopback, link-local, metadata, unspecified, multicast, or reserved address, and use connection-time address pinning (or an equivalently strong connector) so the socket cannot re-resolve to an unapproved address after validation. TLS still uses the original hostname for SNI and certificate verification, the redirect limit remains bounded, and the connected peer address must be one of the approved addresses before request bytes are sent. A stored validation result or earlier DNS answer is never reusable authorization. + +## Detection obligation model — PR #911 active target + +```mermaid +erDiagram + ISSUE_CLAIM ||--o{ DETECTION_OBLIGATION : maps_to + DETECTOR_FAMILY ||--o{ DETECTION_OBLIGATION : satisfies + DETECTION_OBLIGATION ||--o{ DETECTOR_EVIDENCE_CASE : evaluated_by + DETECTOR_EVIDENCE_CASE ||--o{ OBLIGATION_RESULT : produces + WORKFLOW_EVIDENCE ||--o{ DETECTOR_EVIDENCE_CASE : authenticates + + ISSUE_CLAIM { + string repository_full_name + integer issue_number + string canonical_claim_key + string claim_identifier + string issue_state_code + string source_digest + } + + DETECTOR_FAMILY { + string detector_family_id + string execution_owner_code + string detector_version + string capability_status_code + } + + DETECTION_OBLIGATION { + string obligation_id + string repository_full_name + integer issue_number + string claim_identifier + string detector_family_id + string detectability_code + } + + DETECTOR_EVIDENCE_CASE { + string evidence_case_id + string obligation_id + string evidence_type_code + string evidence_digest + string provenance_status_code + } + + OBLIGATION_RESULT { + string obligation_result_id + string evidence_case_id + string result_code + string detector_rule_id + string finding_digest + } + + WORKFLOW_EVIDENCE { + string workflow_evidence_id + string repository_full_name + string workflow_name + string job_name + string head_sha + integer run_id + integer run_attempt + string producer_identity + string producer_capability_code + string source_kind_code + string engine_version + string signed_payload_digest + string signature_status_code + string signature_algorithm_code + string signature_value + string attestation_type_code + string attestation_issuer + string attestation_reference + string conclusion_code + string evidence_digest + } +``` + +This second model is an **active-PR logical contract**, not a protected-develop persisted schema. PR #911 may use committed JSON/registry/fixtures rather than these as database tables. + +### Issue-claim identity and storage scope + +Issue numbers are repository-local. A claim is uniquely scoped by the composite identity `(repository_full_name, issue_number, claim_identifier)`; no implementation may key a retained issue claim by issue number alone. + +`repository_full_name` is the canonical GitHub `owner/repository` identity. `canonical_claim_key` is a stable versioned semantic key from the retained issue-claim registry, not a transient list position. `claim_identifier` is generated deterministically from the versioned namespace plus canonical repository identity, decimal issue number, and canonical claim key. Equivalent registry regeneration must produce the same identifier; a different repository with the same issue number/key must produce a different composite identity. A semantic claim replacement is represented as a new canonical claim key/identifier with explicit supersession rather than silently reusing the old identity. + +The documentation/registry contract tests must cover stable regeneration and cross-repository issue-number collisions before PR #911 can promote this model. + +### Workflow evidence authentication contract + +`WORKFLOW_EVIDENCE` does not become trusted merely because a run is green. Its producer identity/capability, source class, exact workflow/run/head identity, engine/workflow version, canonical signed payload digest, signature/attestation status, algorithm, and signature value are explicit evidence fields. `attestation_type_code` identifies the required attestation contract, `attestation_issuer` is the authorized issuer or key identifier, and `attestation_reference` is an optional immutable transparency-log or provider reference. For `detached_signature`, `signature_value` is the attestation value and `signature_algorithm_code` selects its verifier; the issuer must be authorized for `producer_capability_code`. Workflow/imported evidence requires all applicable attestation fields and a valid signature. A local same-process detector may instead use the closed `not_applicable_local` type/status only when no external trust claim is made. + +Verification must reject a digest mismatch, wrong repository/head/run identity, unauthorized producer capability or issuer, unsupported attestation/signature algorithm, invalid signature, missing required attestation, or mutable reference as `evidence_untrusted`. + +### Canonical evidence serialization and digest linkage + +Producer and verifier implementations use the same byte contract; a phrase such as “canonical digest” is not sufficient: + +1. Schema validation runs first. Text values are converted to Unicode NFC. Object members are then serialized as RFC 8785 JSON Canonicalization Scheme (JCS) bytes in UTF-8, including JCS property ordering, escaping, and number formatting. Non-finite numbers are rejected. Omitted and explicit `null` are distinct: required fields cannot be omitted, optional absent fields are omitted, and `null` is serialized only where the versioned schema explicitly permits it. +2. `bounded_metadata_json` is parsed as bounded I-JSON, recursively normalized by the same rules, and embedded as a JSON value; its source whitespace or member order is never hashed as an opaque string. Duplicate member names, invalid Unicode, out-of-range numbers, and schema-unknown security fields fail closed. +3. Every digest is lowercase hexadecimal SHA-256 over the resulting canonical bytes with no prefix, delimiter, or platform newline. Contract fixtures publish both canonical UTF-8 bytes and the expected digest so independent producer and verifier implementations must match on member-order, omitted-versus-null, numeric, Unicode NFC, and nested-metadata edge cases. + +Digest coverage is versioned and non-circular. `signed_payload_digest` covers the canonical `WORKFLOW_EVIDENCE` envelope fields `repository_full_name`, `workflow_name`, `job_name`, `head_sha`, `run_id`, `run_attempt`, `producer_identity`, `producer_capability_code`, `source_kind_code`, `engine_version`, `conclusion_code`, `evidence_digest`, `attestation_type_code`, and `attestation_issuer`; signed_payload_digest excludes itself plus `signature_status_code`, `signature_algorithm_code`, `signature_value`, and `attestation_reference`. `evidence_digest` covers the immutable `DETECTOR_EVIDENCE_CASE` identity, obligation, evidence type, provenance status, and ordered referenced-artifact digests. `finding_digest` covers the canonical `OBLIGATION_RESULT` identity/result/rule plus the normalized finding envelope and its `evidence_case_id` and `evidence_digest`. The workflow envelope links to a finding only when its `evidence_digest` exactly equals the referenced evidence case and the result's `finding_digest` verifies from that same case; mismatches remain `evidence_untrusted`. + +## Identity and tenancy invariants + +- API key identity resolves organization authority; repository/org strings inside scan payloads cannot elevate access. +- Finding IDs, scan IDs, issue numbers, rule IDs, and GitHub run IDs are evidence identities, not authorization identities. +- Repository identity is part of every retained issue-claim identity; issue number alone is never globally unique. +- Webhook destination strings are protected network destinations and must pass policy before persistence/execution. +- Raw secrets discovered in target code are not copied into durable findings; retain rule/location/fingerprint or bounded redacted evidence instead. + +## Finding provenance + +```mermaid +flowchart LR + SRC[Target source/config] + ENG[Built-in or external engine] + AUTH[Producer capability + evidence authentication] + FIND[Normalized finding] + SCAN[Scan envelope] + SARIF[SARIF/report/control plane] + + SRC --> ENG + ENG --> AUTH + AUTH --> FIND + FIND --> SCAN + SCAN --> SARIF +``` + +A finding retains engine/rule/provenance across transformations. AppGuardrail must not erase the distinction between built-in and external-engine evidence or treat unauthenticated imported evidence as an authenticated detector result. + +## Schema evolution rule + +A future managed PostgreSQL control plane requires explicit migration/rollback, tenant authorization/RLS where used, idempotency/concurrency, webhook/egress security, backup/recovery, retention/deletion, provenance/signature validation, and cross-tenant tests. Conceptual issue-obligation entities become persistent only through such a reviewed migration, not merely by being drawn here. diff --git a/docs/OPERABILITY.md b/docs/OPERABILITY.md new file mode 100644 index 00000000..6183d5cc --- /dev/null +++ b/docs/OPERABILITY.md @@ -0,0 +1,80 @@ +# AppGuardrail Operability, Recovery, and Release Guide + +**Status:** Accepted operating baseline +**Last reviewed:** 2026-08-09 + +## Operating model + +AppGuardrail can run as a local/CI scanner, optional multi-tenant control plane, continuous GitHub monitor, and organization evidence aggregator. The product remains useful when optional external scanners or the control plane are absent; capability/unavailability must be explicit in evidence. + +## Scan health states + +Distinguish: + +- `completed_clean` — selected detector/toolset completed with zero findings; +- `completed_findings` — completed with findings; +- `inconclusive` — evidence malformed/insufficient for a detector obligation; +- `engine_unavailable` — selected optional tool absent/unusable; +- `engine_failed` — tool ran but analysis failed; +- `policy_blocked` — finding set violates deploy gate; +- `evidence_untrusted` — workflow/issue provenance cannot be authenticated. + +Do not collapse non-completion into “clean.” + +## Key SLIs + +- scan completion/findings/blocker counts by engine/family; +- detector false-positive/false-negative benchmark where maintained; +- issue-obligation executable coverage after PR #911 integration; +- engine unavailable/failure rate; +- scan/control-plane latency and finding volume; +- new blocker drift count; +- webhook delivery success/SSRF-policy rejection; +- control-plane auth failures and cross-tenant-denial events; +- remediation/rescan closure rate; +- SBOM/evidence bundle provenance completeness; +- scheduler/API/reviewer infrastructure failures distinct from product findings. + +## Failure recovery + +### Scanner + +Fix the first owning detector/adapter or input-classification boundary, add a regression, then rescan the exact target. Do not suppress a finding merely because a fix is inconvenient. + +### External engine + +Verify installation/version/config/authorization and distinguish provider/tool infrastructure from target vulnerability. One transient rerun may be appropriate after RCA; repeated retries are not a substitute for fixing deterministic failure. + +### Control plane + +Preserve scan/audit state, restore SQLite/managed database from verified backup when necessary, rotate/revoke compromised API keys, and revalidate tenant ownership before resuming writes. Schema changes require forward migration and rollback/recovery evidence. + +### Webhook + +On destination-policy failure, do not send. The current generic unauthenticated webhook path is **at-most-once per local scan event**: after destination validation it makes one best-effort POST and does not automatically retry a transport failure. Record only bounded non-secret delivery evidence; do not create an implicit retry loop that can duplicate receiver-side effects. + +If retries are introduced later, they require a versioned delivery contract before enablement: a stable `delivery_id` persisted across attempts, receiver-side deduplication on that identifier, destination and redirect revalidation for every attempt, bounded response/error evidence, capped retry count/backoff, and explicit terminal failure state. Without those controls, retries remain prohibited rather than “best effort.” + +For both the current one-shot send and any future retry, every send attempt and redirect hop must resolve and evaluate all destination addresses under the current policy. The connector rejects every private, loopback, link-local, metadata, unspecified, multicast, or reserved address and uses connection-time address pinning (or an equivalently strong connector) to prevent DNS rebinding between validation and connect. It retains the original hostname for TLS SNI and certificate verification, bounds redirects, and verifies that the connected peer address is one of the approved addresses before sending request bytes. Contract tests must exercise rebinding, mixed public/private answers, redirects to denied ranges, and peer-address mismatch; a stored `valid` flag or prior DNS result is never authorization for a later connection. + +## Stored SSRF operation + +Webhook destination validation must be revisited when DNS resolution/redirect conditions can change between storage and execution. A stored `valid` flag alone is not permanent authorization to access an arbitrary resolved network endpoint. + +## Issue-to-detector audit operation + +After PR #911 merges, run the executable audit from authenticated retained issue inventory and closed evidence corpus. Fail if a detectable obligation lacks a detector family or detector execution is inconclusive. Historical issue count alone is not a success metric; obligation execution and evidence provenance are. + +## Upgrade and rollback + +1. review CHANGELOG/ADR/detector changes; +2. run full detector/security/control-plane suite; +3. compare finding set on representative benchmark repositories; +4. rehearse persistent schema migration/rollback if changed; +5. canary continuous monitor/control plane where deployed; +6. retain previous package/image/db backup until new evidence is accepted; +7. rollback software/config on regression and re-run the benchmark scan. + +## Release gate + +Release only from exact protected head with all required CI/security/review, 100% production coverage/docs, detector-obligation evidence, package/SBOM/provenance, persistent-state migration/recovery, control-plane auth/network security, CHANGELOG/version, and post-publish smoke. A merged detector PR is not a release by itself. diff --git a/docs/PRD.md b/docs/PRD.md new file mode 100644 index 00000000..77c43ade --- /dev/null +++ b/docs/PRD.md @@ -0,0 +1,126 @@ +# AppGuardrail Product Requirements Document + +**Status:** Accepted cross-cutting product baseline for protected `develop` at `77e3e0c5867b1143970fcdce80962bda8a8fc80f` +**Last reviewed:** 2026-08-12 + +## 1. Product purpose + +AppGuardrail is a persistent security layer for AI-assisted application development. It combines installable security guardrails, deterministic/lightweight static detection, optional external SAST/runtime scanners, normalized findings/SARIF, fix/reverification workflows, continuous GitHub monitoring, multi-tenant scan history/drift, buyer/audit reports, SBOM generation, and organization-wide security-evidence aggregation. + +The product goal is not merely to prevent one defect after a review. Security defect classes surfaced by AppGuardrail's own issue history should become durable, executable detection obligations whenever technically detectable from available evidence. + +## 2. Current protected-branch capabilities + +- CLI initialization/guardrail installation for AI coding tools and stacks; +- lightweight built-in Python/YAML-regex scanning plus optional Trivy/Bandit/Ruff/Semgrep/ZAP integration; +- bounded built-in detection of tested Python stored-webhook persistence patterns through `python-stored-ssrf-webhook-url`; +- normalized findings JSON and SARIF 2.1.0 output; +- deploy gate with severity/config exclusions; +- conservative deterministic autofix for semantics-preserving cases and reviewable fix prompts for behavior changes; +- static local dashboard; +- SQLite-backed multi-tenant control plane for scan ingestion/history/drift/API keys/webhook configuration; +- monitoring/pre-commit workflow installers; +- buyer/founder/agency/fix-pack reports; +- CycloneDX SBOM generation; +- organization buyer-evidence bundle; +- continuous security/process workflows and RCA-first autonomous development policy. + +## 3. Current and active-PR product boundaries + +- PR #924 is **implemented-main** prevention at the control-plane webhook write boundary: malformed bodies, non-string values, and unsafe destinations fail closed before persistence. +- PR #910 is **implemented-main** scanner detection for the separate stored-webhook coding pattern through the packaged `python-stored-ssrf-webhook-url` rule. Its supported scope is the tested Python `set_webhook` direct and one-hop flows, including conditional/non-enforcing guard regressions; it is not a claim of universal interprocedural SSRF detection. +- PR #911 proposes a no-exclusions registry mapping the repository's retained issue history to executable detector-family obligations and authenticated workflow-result evidence. It remains an active-PR product capability until merged and independently verified. +- Open UX/performance Jules PRs remain active-PR and must not be described as current release behavior before integration. + +## 4. Primary users + +- AI-assisted founder/developer needing fast, explainable security feedback. +- Agency/security reviewer needing repeatable client evidence and retest guidance. +- Platform/security engineer needing deploy gates, SARIF, SBOM, continuous monitoring, and drift history. +- Acquisition/buyer/security auditor needing machine-readable evidence and traceability. +- AppGuardrail maintainer needing every valid issue class to remain detectable rather than disappear into historical coordination metadata. + +## 5. Core product invariants + +1. A finding is produced by executable evidence logic, not by a registry row asserting that a condition exists. +2. Prevention/hardening and detection coverage are distinct; fixing one vulnerable endpoint does not satisfy scanner-detection obligations automatically. +3. Every supported detector has realistic positive, negative, and inconclusive evidence. +4. Inconclusive/malformed evidence fails closed and is not promoted to a clean result. +5. External scanner findings preserve engine/source provenance and are not silently relabelled as built-in AppGuardrail detections. +6. Issue/detection coverage may deduplicate repeated incidents into detector families, but retained issue identities/claims remain traceable without waiver-by-omission. +7. Secrets/raw sensitive payloads are not copied into normal findings, logs, reports, or dashboards. +8. Deploy-gate exclusions are explicit configuration and do not erase findings/evidence. +9. AI fix prompts are assistance, not proof; verification must rerun deterministic/security checks. +10. A `Clean Scan` state means completed evidence under the configured detector/toolset, not “no scanner ran” or “workflow failed.” +11. Tenant API keys authorize explicit roles; organization/repository strings are data, not authorization. +12. Webhook/egress destinations are validated at both storage and execution boundaries where applicable. +13. Autonomous development cannot manufacture its own review/merge/release acceptance. + +## 6. Functional requirements + +### PRD-FR-001 Detector engine + +AppGuardrail SHALL support built-in deterministic detector families with stable rule identity, severity, evidence location, remediation, verification guidance, and machine-readable output. A structural `pattern:` fixture is not built-in execution: structural patterns that cannot be represented safely by the lightweight matcher remain external-engine or planned until a real structural engine exists; rule fixtures alone are not detection. + +### PRD-FR-002 Issue-to-detection contract + +Every retained repository issue/claim that represents a detectable application/security anti-pattern SHALL map to an executable detector obligation or a documented non-detectable/external-evidence category with explicit rationale. The audit must exercise actual detector code and independent inventory evidence rather than circular self-assertion. + +### PRD-FR-003 SSRF detection + +AppGuardrail SHALL distinguish direct SSRF, stored SSRF, unsafe webhook/callback URL persistence, DNS/IP allow/deny validation, redirect/rebinding risk, and execution-time egress controls where evidence supports those distinctions. A safe write-path implementation must have corresponding negative/positive scanner tests before the product claims the class is automatically detected. + +### PRD-FR-004 Findings interoperability + +Findings SHALL serialize to normalized JSON and SARIF with deterministic severity/rule/provenance/location data. Optional external engines retain their engine identity. + +### PRD-FR-005 Safe remediation + +Autofix SHALL be restricted to transformations proven semantics-preserving for the targeted rule. Behavior-changing fixes are reviewable prompts/patch guidance and require explicit verification. + +### PRD-FR-006 Continuous monitoring + +Installed GitHub workflows SHALL run AppGuardrail with pinned dependencies/actions, emit evidence, and never turn unavailable/failed required analysis into success. Monitoring remains usable without exposing provider/reviewer credentials to scanned repository code. + +### PRD-FR-007 Control plane + +The control plane SHALL provide tenant-isolated scan history, drift, scoped API keys, webhook notification, bounded payloads, and audit/recovery semantics. SQLite is acceptable for the current standalone profile; enterprise scale may use a managed database behind stable repository interfaces. + +### PRD-FR-008 Evidence reporting + +Buyer/agency/founder/fix-pack/org evidence SHALL be derived from normalized findings and current repository evidence, clearly separate verified facts from gaps/warnings, and omit raw secrets. + +### PRD-FR-009 SBOM/supply chain + +AppGuardrail SHALL produce deterministic component inventory with lockfile provenance where available and preserve tool/source/version evidence required for review or acquisition diligence. + +## 7. Security/privacy requirements + +- scan targets and their source may contain PII/secrets; minimize retention/disclosure rather than blindly copying findings/context; +- control-plane tenant/authz boundaries are explicit and testable; +- webhook URLs and outbound destinations are validated and constrained against SSRF/unsafe egress; +- scanner/external-tool execution is bounded and treats target repository content as untrusted data; +- GitHub Actions/review/model credentials remain outside untrusted target-code execution; +- reports never claim certification (CSAP/SOC 2) from code alone, but may collect control evidence. + +## 8. Quality requirements + +- production statement and branch coverage exactly 100%; +- public API/module docstrings sufficient for beginner-readable behavior; +- positive/negative/inconclusive tests for every detector obligation; +- realistic vulnerable/fixed fixtures and adversarial malformed evidence; +- exact-current-head CI/SAST/security/review evidence; +- benchmark claims require reproducible measurement; loop-count micro-optimizations must not be marketed as wall-clock gains without data. + +## 9. Non-goals + +- pretending every Semgrep-style fixture is executed by the lightweight regex engine; +- replacing specialist external SAST/DAST/dependency scanners where AppGuardrail has no equivalent implementation; +- automatically applying behavior-changing security fixes without review; +- treating no findings as proof of full security; +- using issue metadata alone as proof that a detector works; +- granting model/reviewer bots broad write authority to manufacture merge approval. + +## 10. Release acceptance + +A release requires one exact protected head with full detector/test coverage, control-plane/security regressions, exact CI/security/review, packaging/SBOM/provenance, migration/recovery evidence for changed persistent state, updated CHANGELOG/version/artifacts, and post-publish smoke. PR #910 scanner detection and PR #924 write-boundary prevention are current protected-branch controls; PR #911 claims become current only after protected-branch integration and fresh protected-head verification. diff --git a/docs/TEST_STRATEGY.md b/docs/TEST_STRATEGY.md new file mode 100644 index 00000000..2dcf63f5 --- /dev/null +++ b/docs/TEST_STRATEGY.md @@ -0,0 +1,88 @@ +# AppGuardrail Test and Detector Validation Strategy + +**Status:** Accepted quality baseline +**Last reviewed:** 2026-08-12 + +## Mandatory gates + +- production statement coverage exactly 100%; +- production branch coverage exactly 100%; +- public module/API docstrings 100%; +- complete pytest/security/process suites; +- package/build/install smoke; +- current-head SAST/security/review/branch protection; +- detector-obligation tests independent of source-line coverage. + +No skipped, cancelled, absent, stale, predecessor-head, synthetic-only, action-required, rate-limited, or failed required evidence is passing. + +## Detector test contract + +Every detector family must include: + +1. realistic vulnerable positive fixture; +2. minimally fixed negative fixture; +3. near-miss benign negative fixture to control false positives; +4. malformed/unknown evidence classification where relevant; +5. stable rule/severity/location/evidence assertions; +6. remediation/verification contract; +7. engine/provenance assertion for external tools. + +Fixtures must contain input evidence, not an expected-answer field consumed by the detector. + +## Issue-to-detector validation + +The issue/claim inventory must be independently generated or authenticated and then mapped to obligations. Tests verify every retained detectable claim maps to a detector family and that every obligation executes actual detector code. Duplicate historical incidents may share a detector family but cannot be silently dropped. + +PR #911 is active-PR evidence; until merged, no-exclusions issue coverage is not a protected-branch claim. + +## SSRF tests + +Cover direct and stored variants: + +- user-controlled URL sent immediately; +- user-controlled webhook/callback stored then executed; +- validated-before-store versus validated-before-send; +- loopback/private/link-local/metadata addresses; +- hostname resolving to disallowed IP; +- redirect to disallowed destination; +- allowed HTTPS public destination; +- malformed/ambiguous URL and encoded host/path forms; +- framework/library sink/source variants supported by the detector; +- control-plane write-path regressions integrated through PR #924; +- packaged `python-stored-ssrf-webhook-url` scanner regressions integrated through PR #910, including direct, subscript, attribute, one-hop, ignored-validator, conditional/non-enforcing guard, guarded-then-unguarded sink, and fail-closed cases. + +A prevention test and a scanner-detection test are both required where AppGuardrail claims both controls. The current built-in rule's passing corpus proves its bounded Python `set_webhook` contract, not universal interprocedural SSRF coverage. + +## External engine tests + +Adapters distinguish tool unavailable, tool failed, clean, and findings. Normalize sample outputs without erasing engine/rule/version/source. Runtime target tests such as ZAP use explicitly authorized test hosts only. + +## Control-plane tests + +- role-scoped API keys and cross-tenant negative cases; +- bounded scan ingestion and schema validation; +- drift calculation across scans; +- webhook config URL validation and execution safety; +- API-key bootstrap/revocation/logging; +- idempotency/concurrency where endpoints can be retried; +- SQLite migration/upgrade/backup/recovery for changed persistent state. + +## Remediation tests + +Autofix tests prove preview/apply idempotence and semantics preservation for each supported transformation. Behavior-changing fixes remain guidance and are verified only after an independent source change plus rescan. + +## Reporting/SBOM tests + +Normalize deterministic finding envelopes, SARIF validity, buyer/founder/agency/fix-pack rendering, raw-secret omission, evidence warnings, lockfile/version provenance, SBOM deterministic component identity, and organization bundle manifest integrity. + +## Performance + +For optimizations distinguish operation-count complexity from wall-clock performance. Benchmarks include representative repositories/findings and must preserve identical detector output. A reduced loop count is not marketed as faster without measured time/resource evidence. + +## Automation security tests + +Verify immutable action refs, RCA-first feasibility, no provider/reviewer secrets in untrusted repo execution, exact-head classification, no false-green GitHub API failure, and separation of development from qualifying approval/merge/release. + +## Release acceptance + +A release requires one exact integrated protected head satisfying detector positive/negative obligations, tenant/network security, exact coverage, packaging/SBOM/provenance, migration/rollback where state changes, independent review, and post-publish smoke. \ No newline at end of file diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md new file mode 100644 index 00000000..8aeba721 --- /dev/null +++ b/docs/THREAT_MODEL.md @@ -0,0 +1,62 @@ +# AppGuardrail Threat Model + +**Status:** Accepted baseline +**Last reviewed:** 2026-08-09 + +## Scope + +Covers built-in scanning, optional external engines, findings/SARIF/reporting, deterministic fixes, GitHub monitor workflows, the current SQLite control plane, webhook egress, issue-to-detector assurance, and autonomous-development authority. + +## Trust boundaries + +```mermaid +flowchart LR + CODE[Untrusted target code/config] + SCAN[Scanner/external engines] + FIND[Findings] + CP[Control plane] + OUT[Webhook/ZAP/network target] + DEV[Autonomous developer] + REVIEW[Independent review/merge] + + CODE --> SCAN + SCAN --> FIND + FIND --> CP + CP --> OUT + DEV --> REVIEW +``` + +## Threat inventory + +| Threat | Impact | Controls | +|---|---|---| +| detector fixture asserts its own answer | false issue-coverage confidence | independent inventory + answer-free evidence + actual detector execution | +| unsupported structural rule presented as built-in | false negative/marketing error | explicit built-in vs external-engine capability/maturity | +| scanner/tool unavailable treated as clean | false security assurance | explicit unavailable/inconclusive classification | +| secret extraction/reflection | credential disclosure | redacted/fingerprinted findings; bounded logs/reports | +| malicious target repository | command/file/resource abuse | bounded file discovery/tool invocation; no instruction-following from source | +| stored webhook SSRF | internal network access later | validate before persistence and execution, redirect/DNS/IP policy | +| direct ZAP/target SSRF | unauthorized attack/egress | explicit authorized target, safe URL policy, bounded runtime | +| cross-tenant API-key misuse | scan/history disclosure | authenticated role/organization authority; negative tests | +| API key leakage | tenant compromise | hashed/stored key handling, no console/log disclosure except intended bootstrap file | +| autofix changes semantics | application regression | only proven semantics-preserving deterministic transforms | +| external-engine provenance lost | misleading findings | retain engine/rule/version/source | +| deploy exclusions erase evidence | hidden risk | exclusions affect gate only; finding remains visible | +| tampered SBOM/report evidence | acquisition/security misstatement | deterministic source/lock provenance and manifest hashes | +| autonomous model self-approval | governance bypass | developer/reviewer/merge/release authority separation | + +## Stored SSRF abuse case + +A URL may be safe syntactically but unsafe after DNS resolution, redirect, or later execution. Stored-destination security therefore spans source trust, canonical URL/scheme/port policy, DNS/IP classification, redirect behavior, persistence, and execution-time revalidation/egress. A storage guard and a scanner rule are separate controls. + +## Issue-coverage abuse case + +A retained historical issue can tempt an audit to “cover” itself by mapping an issue to metadata that already states the expected outcome. That is circular assurance. The evidence producer must be independent enough that the detector adapter derives the result from bounded evidence, and workflow incidents require authenticated repository/run/job/head provenance. + +## Residual risk + +No static scanner proves application security. Dynamic/runtime/business-logic vulnerabilities may require external tools or human review. AppGuardrail must state toolset/evidence limits and avoid `Clean Scan` claims when a selected required engine could not run. + +## Review triggers + +Revisit when adding a structural matcher, new external engine, behavior-changing autofix, new network/egress path, persistent tenant schema, new issue-evidence source, or changed autonomous/release credential boundary. \ No newline at end of file diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md new file mode 100644 index 00000000..b9757a95 --- /dev/null +++ b/docs/TRACEABILITY.md @@ -0,0 +1,56 @@ +# AppGuardrail Requirements, Detection, and Evidence Traceability + +**Status:** Accepted cross-cutting baseline +**Last reviewed:** 2026-08-12 + +| Requirement / security class | Detector/control boundary | Evidence maturity | +|---|---|---| +| built-in deterministic scanning | `scanner.py`, rule adapters, normalized findings | implemented-main | +| optional Trivy/Bandit/Ruff/Semgrep/ZAP | external-engine adapters | implemented-main when tool present; capability explicit | +| JSON/SARIF findings | reporting serializers | implemented-main | +| deploy gate/exclusions | gate policy | implemented-main | +| safe deterministic autofix | fix engine | implemented-main for supported transforms only | +| multi-tenant scan/history/drift/API keys | control plane | implemented-main | +| webhook config/notification | control plane/network boundary | implemented-main; storage-boundary SSRF hardening integrated through PR #924 | +| buyer/founder/agency/fix-pack reports | report modules | implemented-main | +| CycloneDX SBOM | SBOM module | implemented-main | +| organization buyer evidence | org evidence aggregator | implemented-main | +| RCA-first feasibility scheduler | CI/agent policy | implemented-main | +| every retained issue claim mapped to executable detector obligation | issue-detection audit | PR #911 active-PR | +| authenticated workflow-result detector evidence | issue-detection audit workflow evidence | PR #911 active-PR | +| automatic scanner detection of unsafe stored-webhook SSRF pattern | built-in `python-stored-ssrf-webhook-url` rule | implemented-main through PR #910 for tested Python `set_webhook` direct and one-hop persistence flows; bounded scope | +| structural Semgrep-style `pattern:` execution by lightweight engine | built-in scanner | not implemented unless a real structural matcher is added; fixtures are not execution | + +## Promotion rules + +- `implemented-main` requires source/tests on protected `develop`, not an issue/PR description. +- `active-PR` becomes current only after merge plus fresh protected-head required evidence. +- External-engine capability must name the engine and availability; normalization does not convert it into a built-in detector. +- A prevention/hardening change does not automatically promote the matching scanner-detection row; PR #924 and PR #910 were verified and promoted independently. +- An issue registry mapping cannot promote an obligation unless actual detector execution derives its result from independent/closed evidence. + +## Issue #911 traceability contract + +When PR #911 is accepted, the authoritative obligation system should preserve issue number/claim identity, detector family, evidence fixture/workflow provenance, execution result, and detector rule/finding evidence. Deduplicating equivalent incidents into one detector family is allowed; dropping a retained claim through an exclusion/waiver list is not. + +## SSRF traceability contract + +For stored webhook/callback SSRF, trace separately: + +1. application prevention at configuration storage; +2. execution-time URL/DNS/IP/redirect/egress validation; +3. AppGuardrail scanner rule capable of finding missing prevention in target code; +4. positive vulnerable fixture; +5. fixed negative fixture; +6. control-plane self-regression; +7. exact-head security/review evidence. + +Current protected-branch evidence keeps those controls distinct: PR #924 supplies the fail-closed webhook storage boundary, and PR #910 supplies the packaged `python-stored-ssrf-webhook-url` detector plus focused regression corpus. Neither control expands the detector beyond its declared source/sink and flow contract. + +## Standards/research + +Existing repository docs/doctoring/security evidence remain the bibliography/source-of-truth for standards such as SARIF, CycloneDX, GitHub security interfaces, and applicable OWASP/CWE classes. Material new detector classes should add authoritative standard/CWE/OWASP references and APA 7 citations in doctoring where research/standards materially drive implementation. + +## Change rule + +Every new issue-class detector or product security boundary should add/update a row and its concrete test/evidence path. Stale/queued/cancelled/rate-limited/predecessor checks cannot promote evidence maturity. \ No newline at end of file diff --git a/docs/TRD.md b/docs/TRD.md new file mode 100644 index 00000000..fb22330f --- /dev/null +++ b/docs/TRD.md @@ -0,0 +1,98 @@ +# AppGuardrail Technical Requirements Document + +**Status:** Accepted cross-cutting technical baseline for protected `develop` +**Last reviewed:** 2026-08-12 + +## 1. Technical objective + +AppGuardrail is a modular security-analysis product with four separable planes: + +```text +scan plane built-in detectors + optional external engines +remediation safe transforms + fix/verification guidance +control plane tenant scan history, drift, API keys, webhooks +assurance plane SARIF, reports, SBOM, issue/detection audit, CI/release evidence +``` + +Each plane must remain usable independently where practical and communicate through normalized typed findings/evidence rather than hidden shared state. + +## 2. Built-in scanner architecture + +The built-in scanner owns deterministic language/path discovery, Python detectors, supported YAML `pattern-regex` rules, finding normalization, gate classification, and configured exclusions. A rule file containing structural `pattern:` syntax is documentation/test data unless an executable structural matcher or external Semgrep integration actually evaluates it. + +Detector contracts include: + +- stable rule/family identifier; +- evidence fields and required/optional shape; +- finding severity/category/location; +- positive/negative/inconclusive semantics; +- remediation and verification guidance; +- external-engine provenance when not built in. + +## 3. Issue-to-detector architecture + +Issue history is an input to requirements traceability, not detector truth. An executable issue-detection audit should use an independent issue inventory and map issue/claim identity to detector family and obligation. It must then call the actual detector adapter over closed evidence fixtures or authenticated workflow evidence. Self-declared `state`, answer-bearing fixtures, or a registry-derived “live” inventory are circular and prohibited. + +PR #911 implements this boundary on an active branch; it is not yet protected-develop behavior. + +## 4. Workflow-result evidence + +Operational/CI incident detector families require authenticated structured results rather than free-form log substring guesses. Evidence should bind exact repository, workflow/job identity, run/attempt, head SHA, conclusion/classification, payload digest, and producer capability/signature when the environment supports it. Unknown/malformed provenance returns inconclusive/fail-closed. + +## 5. SSRF detector requirements + +Scanner rules must distinguish at least: + +```text +user-controlled URL/source +→ validation/canonicalization +→ persistence or immediate request +→ later webhook/callback execution +→ network/redirect/DNS resolution +``` + +Stored SSRF exists when unsafe user-controlled destination data crosses a durable boundary and is later executed. Detection should recognize validation-before-store, validation-before-send, private/link-local/loopback/metadata targets, redirect policy, scheme/port restrictions, hostname/IP resolution semantics, and framework-specific request sinks where feasible. + +Prevention and scanner detection remain separate technical controls. PR #924 integrated the control plane's fail-closed webhook write boundary. PR #910 independently integrated the packaged `python-stored-ssrf-webhook-url` built-in rule and its focused direct, accessor, one-hop, ignored-validator, conditional/non-enforcing guard, guarded-then-unguarded sink, and fail-closed regression corpus. That implementation is bounded to its declared Python `set_webhook` contract and is not a universal interprocedural SSRF analyzer. + +## 6. External engine adapters + +Trivy/Bandit/Ruff/Semgrep/ZAP/CodeGraph integrations are optional and capability-detected. Adapter output is normalized without losing engine/rule/version/source provenance. Tool absence is distinguishable from a clean result. Authorized running URL is required for ZAP/runtime checks; AppGuardrail does not discover or attack arbitrary targets. + +## 7. Gate semantics + +Findings remain visible even when excluded from deploy blocking. Default blocking policy focuses on application production code while docs/tests/examples/fixtures remain evidence but do not fail the deploy gate unless configured. Invalid `.appguardrail.json` fails loudly. + +`Clean Scan` requires successful completion of the selected detector/toolset; failure/unavailability cannot be rendered as clean merely because findings are absent. + +## 8. Remediation architecture + +Deterministic autofix is allowed only for transformations whose semantic preservation is covered by tests. Other changes produce reviewable structured guidance (`Problem`, `Fix Prompt`, `Verification`) and must rerun the detector after user/agent changes. + +## 9. Control-plane architecture + +Current standalone profile uses Python stdlib + SQLite behind repository functions. It provides organization/API-key roles, scan ingestion/history, deploy-blocking drift, webhook configuration/notification, health, and static dashboard/API use. + +Persistent state must enforce organization ownership independent from request-provided repo/org strings. Key material is stored/returned only through intended bootstrap/key-management paths. Webhook destination validation occurs before persistence and again as appropriate before network execution. + +## 10. Reporting/SBOM + +Normalized findings are the source contract for reports/dashboard/control-plane ingestion. Reports and org evidence bundle separate verified counts, source warnings, and unavailable evidence. SBOM inventory records lock/manifests and component provenance according to supported formats; no invented version is accepted as a locked version. + +## 11. Security boundaries + +- repository-under-scan is untrusted input; +- no raw secret should be emitted by a finding/report/log when a fingerprint/location suffices; +- external tool invocation and output are bounded; +- control-plane HTTP input is tenant-authenticated and size-bounded; +- outbound webhook/DAST targets are authorization/SSRF boundaries; +- GitHub/NVIDIA/reviewer credentials do not enter scanned repository-controlled execution; +- autonomous development uses RCA-first feasibility and independent merge/release authority. + +## 12. Quality/evidence + +Every detector family requires realistic positive/negative/unknown tests and exact production statement/branch coverage. Security detector coverage is measured by obligations, not just source line coverage. Performance changes require operation-count or wall-clock evidence matching the claim. + +## 13. Change control + +Changes to detector truth semantics, issue-coverage policy, external-engine provenance, autofix authority, tenant/authz, webhook/egress, persistent schemas, evidence/report formats, or automation credentials require an ADR and PRD/TRD/Architecture/UML/ERD/Threat/Test/Operability/Traceability reconciliation. \ No newline at end of file diff --git a/docs/UML.md b/docs/UML.md new file mode 100644 index 00000000..5047ca89 --- /dev/null +++ b/docs/UML.md @@ -0,0 +1,169 @@ +# AppGuardrail UML and Runtime Views + +**Status:** Accepted cross-cutting diagrams; active-PR boundaries labelled. +**Last reviewed:** 2026-08-09 + +## Scan sequence + +```mermaid +sequenceDiagram + actor User + participant CLI + participant Discovery + participant Builtin as Built-in detectors + participant External as Optional external engines + participant Findings + participant Gate + + User->>CLI: scan target + options + CLI->>Discovery: enumerate bounded supported files/config + Discovery->>Builtin: normalized source/evidence + Builtin-->>Findings: built-in findings + opt installed/authorized external tools + CLI->>External: bounded scan request + External-->>Findings: engine-provenance findings + end + Findings->>Findings: normalize/deduplicate without erasing provenance + Findings->>Gate: configured fail_on/exclusions + Gate-->>User: findings + deploy outcome + evidence outputs +``` + +## Issue-obligation sequence — PR #911 active target + +```mermaid +sequenceDiagram + participant Inventory as Independent issue inventory + participant Registry as Obligation registry + participant Adapter as Detector adapter + participant Detector as Actual executable detector + participant Evidence as Closed/authenticated evidence + + Inventory->>Registry: retained repository + issue + claim identities + Registry->>Registry: map each to detector family/obligation + Registry->>Adapter: detector obligation + Evidence->>Adapter: evidence only; no expected answer + Adapter->>Detector: execute real detector + Detector-->>Adapter: finding / clean / inconclusive + Adapter-->>Registry: obligation result + authenticated evidence digest +``` + +The registry cannot create `PASS` by declaring an issue `implemented` or by embedding the expected finding in fixture metadata. + +## Safe remediation state machine + +```mermaid +stateDiagram-v2 + [*] --> finding + finding --> deterministic_fix_candidate: semantics-preserving transformer exists + finding --> reviewable_guidance: behavior change required + deterministic_fix_candidate --> preview + preview --> applied: explicit --apply + preview --> rejected + applied --> rescan + reviewable_guidance --> external_change + external_change --> rescan + rescan --> verified_fixed: detector no longer finds issue and regression passes + rescan --> still_failing + verified_fixed --> [*] + rejected --> [*] + still_failing --> finding +``` + +## Control-plane scan ingestion and webhook sequence + +```mermaid +sequenceDiagram + actor CI + participant API as AppGuardrail control plane + participant Auth as API-key role resolver + participant DB as SQLite/current store + participant Drift + participant Webhook as Configured notifier + + CI->>API: POST scan + bearer key + API->>Auth: authenticate and resolve organization/role + Auth-->>API: tenant authority + API->>API: validate bounded normalized findings + API->>DB: persist scan under authenticated tenant + DB-->>Drift: previous/current blocker evidence + Drift-->>API: drift result + opt new blockers + safe configured webhook + API->>API: validate current destination policy + API->>Webhook: one best-effort POST (at-most-once current contract) + alt transport success + Webhook-->>API: delivery success + else destination/transport failure + Webhook-->>API: bounded failure + Note over API,Webhook: no automatic retry in protected current path + end + end + API-->>CI: scan identity/outcome without secrets +``` + +A future retry-capable notifier must add one stable persisted `delivery_id`, receiver-side deduplication, per-attempt destination/redirect revalidation, and capped retry/backoff before this diagram may show a retry loop. + +## Detection maturity state + +```mermaid +stateDiagram-v2 + [*] --> historical_issue + historical_issue --> detector_obligation: claim is technically detectable + historical_issue --> external_or_nondetectable: explicit rationale + detector_obligation --> tests_verified: positive/negative/inconclusive contract suite passes + detector_obligation --> tests_failed: detector/evidence contract fails + tests_failed --> detector_obligation: implementation or evidence repaired + tests_verified --> executable_detector + executable_detector --> exact_head_verified + exact_head_verified --> protected_branch_detector + protected_branch_detector --> monitored_regression +``` + +`tests_verified` means the required positive, negative, and inconclusive-evidence expectations completed and passed. A RED regression is a development step before implementation, not a maturity state that can promote a detector to executable capability. PR/issue text alone does not move a claim to `protected_branch_detector`. + +## Deployment view + +```mermaid +flowchart TB + subgraph local[Local/CI] + CLI[AppGuardrail CLI] + TARGET[Target repository] + OPTIONAL[Trivy / Semgrep / Bandit / Ruff / ZAP] + TARGET --> CLI + OPTIONAL --> CLI + end + + subgraph control[Optional control plane] + API[HTTP API] + DB[(SQLite current / managed DB future)] + DASH[Static org console] + API --> DB + DASH --> API + end + + CLI -->|normalized scan push when configured| API +``` + +## Authority flow + +```mermaid +flowchart LR + TARGET[Untrusted target code] + DET[Detector execution] + FIND[Finding evidence] + HUMAN[Builder/security owner] + FIX[Fix path] + CI[Re-verification] + + TARGET --> DET + DET --> FIND + FIND --> HUMAN + HUMAN --> FIX + FIX --> CI + CI --> DET +``` + +A finding can trigger guidance but does not grant mutation authority. A clean rerun plus required repository gates is the verification loop. + +## Maintenance rule + +When a new scanner, persistent service, detection-obligation class, outbound executor, fix authority, tenant boundary, evidence-authentication contract, or webhook delivery semantic changes, update these diagrams with PRD/TRD/Architecture/ERD/Threat/Test/Operability/ADR/Traceability in the same reviewed change. \ No newline at end of file diff --git a/docs/adr/0001-executable-detector-truth.md b/docs/adr/0001-executable-detector-truth.md new file mode 100644 index 00000000..1c1a3e04 --- /dev/null +++ b/docs/adr/0001-executable-detector-truth.md @@ -0,0 +1,16 @@ +# ADR-0001: Detection truth must come from executable evidence + +**Status:** Accepted +**Date:** 2026-08-09 + +## Context + +A security registry, issue record, or fixture can state that a vulnerability should exist, but consuming that statement as the detector result creates circular assurance. + +## Decision + +A finding/obligation result is produced only by actual detector logic over answer-free bounded evidence or authenticated structured workflow evidence. Registries map requirements to detector families and evidence sources; they do not assert pass/fail themselves. + +## Consequences + +Issue-to-detector coverage must execute real detector adapters. Unknown/untrusted evidence is inconclusive/fail-closed. Audit tests must prevent registry-derived fake “live” inventories or expected-answer fixture fields from satisfying the contract. \ No newline at end of file diff --git a/docs/adr/0002-prevention-versus-detection.md b/docs/adr/0002-prevention-versus-detection.md new file mode 100644 index 00000000..835e7969 --- /dev/null +++ b/docs/adr/0002-prevention-versus-detection.md @@ -0,0 +1,6 @@ +# ADR-0002: Treat prevention and scanner detection as separate obligations + +**Status:** Accepted +**Date:** 2026-08-09 + +A vulnerable AppGuardrail endpoint can be hardened without making AppGuardrail capable of finding the same unsafe pattern in other software. Therefore product-control prevention and scanner detection are separately traceable and separately tested. A fixed webhook storage boundary does not satisfy stored-SSRF scanner coverage until an executable detector has positive and fixed negative target-code tests. \ No newline at end of file diff --git a/docs/adr/0003-external-engine-provenance.md b/docs/adr/0003-external-engine-provenance.md new file mode 100644 index 00000000..bda41ee5 --- /dev/null +++ b/docs/adr/0003-external-engine-provenance.md @@ -0,0 +1,6 @@ +# ADR-0003: Preserve external scanner provenance + +**Status:** Accepted +**Date:** 2026-08-09 + +Optional engines such as Trivy, Bandit, Ruff, Semgrep, and ZAP remain distinct evidence producers. AppGuardrail normalizes their findings for common reporting/gating without claiming those analyses were produced by its lightweight built-in matcher. Tool unavailable/failed/clean/finding states remain distinguishable and engine/rule/version/source provenance survives serialization. \ No newline at end of file diff --git a/docs/adr/0004-tenant-network-boundaries.md b/docs/adr/0004-tenant-network-boundaries.md new file mode 100644 index 00000000..a25204c6 --- /dev/null +++ b/docs/adr/0004-tenant-network-boundaries.md @@ -0,0 +1,6 @@ +# ADR-0004: Treat tenant authority and outbound destinations as explicit security boundaries + +**Status:** Accepted +**Date:** 2026-08-09 + +Control-plane organization authority derives from authenticated API-key/role context, never from untrusted request repository/organization strings. Webhook, callback, ZAP, and other outbound destinations are separately authorized/validated network boundaries. Stored destinations must be validated before persistence and rechecked as needed before execution because DNS/redirect/network conditions can change. \ No newline at end of file diff --git a/docs/adr/0005-remediation-authority.md b/docs/adr/0005-remediation-authority.md new file mode 100644 index 00000000..bd46c7a4 --- /dev/null +++ b/docs/adr/0005-remediation-authority.md @@ -0,0 +1,6 @@ +# ADR-0005: Limit deterministic autofix to proven semantics-preserving transforms + +**Status:** Accepted +**Date:** 2026-08-09 + +AppGuardrail may automatically preview/apply only transformations whose behavior preservation is narrowly defined and regression-tested for the detector class. Security fixes that change application behavior, routing, authorization, persistence, or business semantics remain reviewable guidance/patches and become accepted only after the target detector and relevant application tests pass again. \ No newline at end of file diff --git a/docs/adr/0006-automation-authority.md b/docs/adr/0006-automation-authority.md new file mode 100644 index 00000000..34c994a1 --- /dev/null +++ b/docs/adr/0006-automation-authority.md @@ -0,0 +1,6 @@ +# ADR-0006: Separate autonomous development from independent review, merge, and release authority + +**Status:** Accepted +**Date:** 2026-08-09 + +Autonomous development may perform RCA, create test-first source changes, run credential-free verification, and publish ordinary reviewable work through bounded trusted tooling. It cannot manufacture a qualifying approval, bypass required checks, force protected merge, tag, or publish. Model-provider credentials and reviewer/merge/release credentials remain separate identities/scopes. \ No newline at end of file diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 00000000..984420a9 --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,18 @@ +# AppGuardrail Architecture Decision Record Index + +`Accepted` means the decision governs architecture; implementation maturity remains separate and is tracked in PRD/Traceability. + +| ADR | Decision | Status | +|---|---|---| +| [0001](0001-executable-detector-truth.md) | Detection truth comes from executable evidence, not registry assertions | Accepted | +| [0002](0002-prevention-versus-detection.md) | Prevention/hardening and scanner detection are separate obligations | Accepted | +| [0003](0003-external-engine-provenance.md) | External scanner provenance remains explicit | Accepted | +| [0004](0004-tenant-network-boundaries.md) | Tenant authority and outbound destinations are explicit security boundaries | Accepted | +| [0005](0005-remediation-authority.md) | Deterministic autofix is limited to proven semantics-preserving transforms | Accepted | +| [0006](0006-automation-authority.md) | Autonomous development remains separate from independent merge/release authority | Accepted | + +## ADR triggers + +Create or update an ADR when changing detector truth semantics, issue obligation coverage, built-in versus external execution, autofix authority, persistent tenant schema/authz, outbound webhook/DAST egress, normalized finding/SARIF identity, or autonomous/release credentials. + +Implementation PRs must reconcile PRD/TRD/Architecture/UML/ERD/Threat/Test/Operability/Traceability and CHANGELOG where those contracts move. \ No newline at end of file diff --git a/tests/test_documentation_contract.py b/tests/test_documentation_contract.py new file mode 100644 index 00000000..2ffa7719 --- /dev/null +++ b/tests/test_documentation_contract.py @@ -0,0 +1,215 @@ +"""Contract tests for AppGuardrail's canonical product and detector documentation.""" + +from __future__ import annotations + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +REQUIRED_DOCUMENTS = ( + "DOCUMENTATION.md", + "docs/PRD.md", + "docs/TRD.md", + "ARCHITECTURE.md", + "docs/UML.md", + "docs/ERD.md", + "docs/THREAT_MODEL.md", + "docs/TEST_STRATEGY.md", + "docs/OPERABILITY.md", + "docs/TRACEABILITY.md", + "docs/adr/README.md", + "SECURITY.md", + "docs/release-automation.md", + "docs/product/2026-07-02-2b-krw-sale-readiness-plan.md", + "README.md", + "AGENTS.md", + "CLAUDE.md", + "CHANGELOG.md", +) +GOVERNING_ADRS = ( + "0001-executable-detector-truth.md", + "0002-prevention-versus-detection.md", + "0003-external-engine-provenance.md", + "0004-tenant-network-boundaries.md", + "0005-remediation-authority.md", + "0006-automation-authority.md", +) + + +def _read(relative_path: str) -> str: + """Return one repository document as UTF-8 text.""" + + return (ROOT / relative_path).read_text(encoding="utf-8") + + +def _single_line_with(text: str, *markers: str) -> str: + """Return the one documentation line containing every requested marker.""" + + matches = [ + line.strip() + for line in text.splitlines() + if all(marker in line for marker in markers) + ] + assert len(matches) == 1, ( + f"expected one line containing {markers!r}, found {len(matches)}" + ) + return matches[0] + + +def test_canonical_detection_documents_exist() -> None: + """Keep product, technical, detection, and operating memory discoverable.""" + + missing = [path for path in REQUIRED_DOCUMENTS if not (ROOT / path).is_file()] + assert not missing, f"missing canonical documentation: {missing}" + + +def test_documentation_map_links_cross_cutting_contracts() -> None: + """Require the documentation map to link every canonical mapped record.""" + + documentation = _read("DOCUMENTATION.md") + for path in REQUIRED_DOCUMENTS[1:]: + assert f"]({path})" in documentation, ( + f"documentation map does not contain an actual Markdown link to {path}" + ) + + +def test_integrated_ssrf_controls_are_promoted_but_distinct() -> None: + """Promote merged SSRF controls without conflating prevention and detection.""" + + architecture = _read("ARCHITECTURE.md") + prd = _read("docs/PRD.md") + traceability = _read("docs/TRACEABILITY.md") + + prevention_claim = _single_line_with(prd, "PR #924", "implemented-main") + assert "prevention" in prevention_claim and "webhook write boundary" in prevention_claim + + detector_claim = _single_line_with(prd, "PR #910", "implemented-main") + assert "scanner detection" in detector_claim + assert "python-stored-ssrf-webhook-url" in detector_claim + + active_issue_claim = _single_line_with(prd, "PR #911", "active-PR") + assert "no-exclusions registry" in active_issue_claim + + detector_trace = _single_line_with( + traceability, + "automatic scanner detection of unsafe stored-webhook SSRF pattern", + "PR #910", + ) + assert "python-stored-ssrf-webhook-url" in detector_trace + assert "implemented-main" in detector_trace and "bounded scope" in detector_trace + + issue_trace = _single_line_with( + traceability, + "every retained issue claim mapped to executable detector obligation", + "PR #911 active-PR", + ) + assert "issue-detection audit" in issue_trace + assert "separate controls" in architecture + + +def test_structural_rule_fixture_is_not_claimed_as_lightweight_execution() -> None: + """Prevent Semgrep-style fixtures from becoming false built-in capability claims.""" + + prd = _read("docs/PRD.md") + architecture = _read("ARCHITECTURE.md") + assert "structural `pattern:`" in prd + assert "not automatically executable in full" in architecture + + +def test_issue_claim_identity_is_repository_scoped_and_stable() -> None: + """Keep future issue obligations collision-safe across GitHub repositories.""" + + erd = _read("docs/ERD.md") + assert "(repository_full_name, issue_number, claim_identifier)" in erd + assert "canonical_claim_key" in erd + assert "generated deterministically" in erd + assert "same issue number/key must produce a different composite identity" in erd + assert "stable regeneration" in erd + + +def test_evidence_provenance_is_not_hidden_in_free_form_metadata() -> None: + """Require explicit producer, digest, version, and authentication fields.""" + + erd = _read("docs/ERD.md") + for field in ( + "engine_version", + "source_kind_code", + "producer_capability_code", + "producer_identity", + "signed_payload_digest", + "signature_status_code", + "signature_algorithm_code", + "signature_value", + "attestation_type_code", + "attestation_issuer", + "attestation_reference", + ): + assert field in erd, f"missing explicit evidence provenance field {field}" + assert "bounded_metadata_json` is supplementary metadata" in erd + assert "evidence_untrusted" in erd + + +def test_evidence_digest_serialization_is_deterministic_and_linked() -> None: + """Bind producer and verifier digests to one byte-level evidence contract.""" + + erd = _read("docs/ERD.md") + lowered = erd.lower() + for phrase in ( + "RFC 8785", + "UTF-8", + "Unicode NFC", + "omitted and explicit `null` are distinct", + "non-finite numbers are rejected", + "bounded_metadata_json", + "SHA-256", + "signed_payload_digest excludes", + "evidence_digest", + "finding_digest", + "producer and verifier", + ): + assert phrase.lower() in lowered + + +def test_webhook_retry_semantics_match_current_one_shot_implementation() -> None: + """Prevent docs from inventing unsafe retry behavior without idempotency.""" + + erd = _read("docs/ERD.md") + operability = _read("docs/OPERABILITY.md") + uml = _read("docs/UML.md") + for document in (erd, operability, uml): + assert "at-most-once" in document + assert "does not automatically retry" in operability + assert "stable `delivery_id`" in operability + assert "receiver-side deduplication" in operability + assert "no automatic retry" in uml + erd_lowered = erd.lower() + operability_lowered = operability.lower() + for phrase in ( + "every send attempt and redirect hop", + "connection-time address pinning", + "private, loopback, link-local, metadata, unspecified, multicast, or reserved", + "connected peer address", + ): + assert phrase.lower() in erd_lowered + assert phrase.lower() in operability_lowered + + +def test_detector_maturity_requires_verified_tests() -> None: + """Keep a failing RED test from being represented as an executable detector.""" + + uml = _read("docs/UML.md") + assert "detector_obligation --> tests_verified" in uml + assert "tests_verified --> executable_detector" in uml + assert "detector_obligation --> tests_failed" in uml + assert "tests_red --> executable_detector" not in uml + assert "tests_failed --> executable_detector" not in uml + + +def test_adr_index_contains_governing_detector_decisions() -> None: + """Keep the detector/security architecture decisions present and indexed.""" + + index = _read("docs/adr/README.md") + for adr in GOVERNING_ADRS: + adr_path = ROOT / "docs" / "adr" / adr + assert adr_path.is_file(), f"ADR file is missing: {adr}" + assert f"]({adr})" in index, f"ADR index does not link {adr}"