Skip to content
Open
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
18 changes: 17 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ flowchart LR

A registry maps requirement identity to executable detector family; it cannot assert the detector answer. PR #911 is active-PR implementation of this contract.

### Source-bound workflow evidence

The organization security collector acquires GitHub Actions run and job
responses through the pinned REST client, then derives a canonical
source-bound evidence envelope from the job conclusion. The envelope includes
fixed `probe_ref`/`acquirer_ref`, repository and revision identity, a generated
run/job artifact reference, bounded-source SHA-256, freshness, and a typed
`clean`/`detected`/`unknown` assessment. The hash covers selected run/job
metadata and step conclusions only; logs and caller-provided result fields are
never evidence. `detected` is a failed security control, not proof of a
vulnerability. Legacy collector findings remain a compatibility boundary.

## SSRF architecture

```mermaid
Expand All @@ -87,6 +99,10 @@ Stored SSRF prevention and scanner detection are separate controls. The control-

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.

Canonical source-bound workflow evidence is preserved inside normalized scan
findings and returned by authenticated scan detail. The control plane does not
reclassify an `unknown` source assessment as clean or as a deploy blocker.

## 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.
Expand Down Expand Up @@ -120,4 +136,4 @@ 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.
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.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- 대시보드 검색창 커서 유지 — 검색어 중간에서 텍스트를 수정할 때마다 커서가 검색어의 맨 끝으로 점프하는 불편함을 수정했습니다. 이제 입력창의 커서 위치(`selectionStart`/`selectionEnd`)가 동적 렌더링 이후에도 원래 위치에 정확히 유지되어 자연스러운 타이핑 경험을 제공합니다.

### 보안
- Issue #938 source-bound workflow evidence vertical slice — GitHub Actions 보안 workflow run/job 결과를 고정된 `probe_ref`·`acquirer_ref`, repository/revision, 생성된 run/job artifact identity, SHA-256, freshness, typed `clean`/`detected`/`unknown` assessment로 묶습니다. caller-provided pass/fail·digest와 raw job log는 신뢰하거나 저장하지 않으며, 실패한 workflow control을 confirmed vulnerability로 과장하지 않습니다. IssueOps 및 control-plane scan detail이 canonical evidence를 보존하고, malformed/stale/duplicate/unavailable/ambiguous/unknown-family 입력은 fail closed 합니다.
- 리포트 출력 하드닝 — 생성된 markdown 리포트가 HTML로 렌더될 때 악성 finding 내용(예: 외부 엔진이 스캔한 코드의 `<script>`)이 주입되지 않도록, 프로즈 필드(message/remediation/verification)를 HTML 이스케이프하고 snippet의 code-fence 탈출을 무력화합니다(모든 리포트 타입). rule_id/category/context 등 제약된 식별자는 그대로 둡니다.
- control plane API 하드닝: (1) 요청 본문을 10MiB로 캡하고 음수 Content-Length를 거부합니다(유효 키 소지자의 OOM/EOF-hang 방지). (2) `limit`/`offset` 쿼리 파라미터를 클램프합니다 — sqlite에서 `LIMIT -1`은 무제한이므로 음수를 그대로 전달하면 페이지네이션 캡이 우회됐습니다(list 1..1000, trend 1..365, offset ≥0).

Expand Down
24 changes: 24 additions & 0 deletions appguardrail_core/issueops.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,30 @@ def summary(finding: dict[str, Any]) -> str:
("Run", finding["run_url"]),
("Job", finding["job_url"]),
]
evidence = finding.get("source_evidence")
if isinstance(evidence, dict):
assessment = evidence.get("assessment")
identity = evidence.get("source_identity")
if isinstance(assessment, dict) and isinstance(identity, dict):
rows.extend(
[
(
"Source evidence status",
f"`{assessment.get('status', 'unknown')}`",
),
(
"Source evidence reason",
f"`{assessment.get('reason', 'unknown')}`",
),
("probe_ref", f"`{evidence.get('probe_ref', 'unknown')}`"),
("acquirer_ref", f"`{evidence.get('acquirer_ref', 'unknown')}`"),
(
"Source artifact SHA-256",
f"`{identity.get('artifact_sha256') or 'unknown'}`",
),
("Source revision", f"`{identity.get('revision') or 'unknown'}`"),
]
)
return "\n".join(f"- {key}: {value}" for key, value in rows)


Expand Down
Loading
Loading