Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b638d19
feat(security): detect unlocked runtime npm execution
seonghobae Oct 9, 2026
a754376
feat(security): detect unlocked runtime npm execution
seonghobae Oct 9, 2026
53214f7
feat(security): detect unlocked runtime npm execution
seonghobae Oct 9, 2026
d4594e3
feat(security): detect unlocked runtime npm execution
seonghobae Oct 9, 2026
5700fa7
feat(security): detect unlocked runtime npm execution
seonghobae Oct 9, 2026
255d1bd
feat(security): detect unlocked runtime npm execution
seonghobae Oct 9, 2026
e6c564c
feat(security): detect unlocked runtime npm execution
seonghobae Oct 9, 2026
3a79d83
feat(security): detect unlocked runtime npm execution
seonghobae Oct 9, 2026
e8ac840
fix(security): apply reviewed detector boundary
seonghobae Oct 9, 2026
beee524
fix(security): apply reviewed detector boundary
seonghobae Oct 9, 2026
cf39443
fix(security): apply reviewed detector boundary
seonghobae Oct 9, 2026
e63ec16
fix(security): apply reviewed detector boundary
seonghobae Oct 9, 2026
2833895
fix(security): apply reviewed detector boundary
seonghobae Oct 9, 2026
633e04a
fix(security): restore canonical protected workflow
seonghobae Oct 9, 2026
99d7395
fix(security): remove untrusted consumer bootstrap
seonghobae Oct 9, 2026
1a923ea
fix(security): remove untrusted consumer bootstrap
seonghobae Oct 9, 2026
f72a809
fix(security): bind runtime package finding to one step
seonghobae Oct 9, 2026
e0ddeb3
fix(security): bind runtime package finding to one step
seonghobae Oct 9, 2026
d254ece
fix(security): bind runtime package finding to one step
seonghobae Oct 9, 2026
57e50e1
fix(security): bind runtime package finding to one step
seonghobae Oct 9, 2026
9c193e2
fix(security): bind runtime package finding to one step
seonghobae Oct 9, 2026
3da2e42
fix(security): analyze runtime package shell state
seonghobae Oct 9, 2026
c4e43f6
fix(security): analyze runtime package shell state
seonghobae Oct 9, 2026
e4ce3fb
fix(security): analyze runtime package shell state
seonghobae Oct 9, 2026
f76eb77
fix(security): analyze runtime package shell state
seonghobae Oct 9, 2026
2eb4f83
fix(security): analyze runtime package shell state
seonghobae Oct 9, 2026
a177c5d
fix(security): analyze runtime package shell state
seonghobae Oct 9, 2026
ea3843c
fix(security): accept compact action step mappings
seonghobae Oct 9, 2026
2d9c46c
fix(security): accept compact action step mappings
seonghobae Oct 9, 2026
f83412a
fix(security): accept compact action step mappings
seonghobae Oct 9, 2026
02d04fb
fix(security): accept compact action step mappings
seonghobae Oct 9, 2026
a0fab92
fix(security): accept compact action step mappings
seonghobae Oct 9, 2026
4ddc75b
feat(security): detect privileged PR-head execution
seonghobae Oct 9, 2026
40ad888
fix(security): preserve PR-target causal boundaries
seonghobae Oct 9, 2026
96fb137
fix: tighten pull request target causal boundaries
seonghobae Oct 9, 2026
0d45c3b
fix: bind PR target execution to selected tree
seonghobae Oct 9, 2026
7066131
fix: track PR worktree execution context
seonghobae Oct 9, 2026
a850c1a
fix: close PR-head provenance parser gaps
seonghobae Oct 9, 2026
9dfcd9a
fix: preserve exact PR tree provenance
seonghobae Oct 9, 2026
a47de52
fix: retain current PR revision provenance
seonghobae Oct 9, 2026
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
45 changes: 44 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,49 @@ The detector that observes evidence is authoritative for its finding. `scanner/r

External engines retain their own engine/rule/version provenance. AppGuardrail normalizes their output but does not claim their analysis was performed internally.

The proposed
`github-actions-pull-request-target-untrusted-head-execution` built-in rule
owns a job-local GitHub Actions trust-boundary signal. A deterministic analyzer
requires all four causal elements: a `pull_request_target` trigger, repository
privilege in the same job, explicit materialization of an event-derived PR head,
and subsequent execution of a local action, script, test, or build from that
tree. It recognizes equivalent dot/bracket event expressions and one-hop
workflow-, job-, or same-step block/flow (including multiline flow) `env` bindings used by the same shell command
segment as Git tree selection. A fetch alone is insufficient; checkout/switch
must select the revision, or a worktree must select it and execution must bind
through normalized `cd`, `pushd`, exact step `working-directory`, or an executable/script path.
The most recent fetch that writes `FETCH_HEAD` owns its provenance; a later trusted
overwriting fetch clears prior PR-head evidence, while `--no-write-fetch-head` and
`--append` (including bundled short options) preserve it. A later directly executed
checkout/switch that selects a different tree invalidates an earlier PR-tree
selection; path restore, branch creation at current HEAD, inert text, and repeated
PR-head selection do not. Interpreter options do not break executable-path binding.
Action checkout materialization is bound
specifically to its owned `with.ref` input, including flow mappings. It
does not merge evidence across jobs or treat
YAML-looking action inputs, comments, heredocs, or run-block data as workflow authority.
Same-repository author predicates do not change the result because collaborator
branches remain mutable. Ordinary `pull_request`, metadata-only
`pull_request_target`, trusted base checkout, authority isolated in another job,
and a conservatively recognized scalar or block job-level event guard whose
every disjunct contains a standalone non-PR-target event atom remain
outside the finding boundary. The canonical prevention boundary remains a
metadata-only trigger followed by a separately reviewed default-branch
execution workflow.

The proposed `github-actions-runtime-package-without-integrity` built-in rule owns
the narrow workflow-source signal where one step binds a versioned registry
package environment value to the exact package-selector position of that step's
`npx`/`npm exec` auto-install command. A deterministic line/state analyzer tracks
step-local field indentation, blank block-scalar lines, exact variable boundaries,
compact or expanded step mappings, intervening YAML comments, and shell heredoc
state. Heredoc body text is ignored unless an unquoted body is
redirected to a target that the same step later makes executable with `chmod`.
Lexical proximity is therefore not promoted to causal execution. The rule does
not claim that a download or registry compromise occurred. Central protected workflow prevention, immutable tool
distribution, consumer configuration, and this repository-local
detector remain separate controls.

## Issue-to-detection boundary

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

### 보안
- GitHub Actions `pull_request_target` 신뢰 경계 검출 — 동일 job에서 repository write/OIDC/secret 권한을 가진 채 event-derived PR head를 action-owned `with.ref` 또는 workflow/job/same-step 수준 block/flow(one-line/multiline) one-hop `env` alias로 checkout/switch하거나, 가장 최근 `FETCH_HEAD`-writing PR-head fetch의 worktree를 만들고 normalized `cd`/`pushd`/exact step `working-directory`/interpreter-option-aware executable path로 결합한 뒤 그 트리의 local action·script·test·build를 실행하는 causal chain을 `github-actions-pull-request-target-untrusted-head-execution`(HIGH, CWE-829/CWE-269)으로 탐지합니다. `--no-write-fetch-head`/`--append`(bundled short option 포함)는 기존 provenance를 보존하고, 실제 실행되어 다른 tree를 선택하는 checkout/switch 또는 overwriting fetch만 이를 무효화합니다. path restore, current-HEAD branch 생성, inert command text, 동일 PR-head 재선택은 provenance를 보존합니다. same-repository 조건은 collaborator branch의 가변성을 제거하지 않으므로 예외로 보지 않습니다. metadata-only 처리, fetch-only, later trusted selection, 일반 `pull_request`, trusted-base checkout, 다른 job에 격리된 권한, 모든 disjunct가 standalone non-PR-target event atom을 갖는 보수적 scalar/block job guard는 음성 회귀로 고정했고, unrelated boolean negation, dot/bracket expression, top-level trigger 형식·field 순서·flow mapping·trailing comment는 우회가 되지 않으며 action input/comment/heredoc/run data의 YAML 유사 텍스트는 권한 증거로 승격하지 않습니다.
- GitHub Actions 공급망 검출 — deterministic step/run analyzer가 compact `- env:`와 expanded `env:`를 포함해 한 step의 버전 고정 registry package `*_PACKAGE` 환경값이 같은 step의 `npx -y/--yes` 또는 `npm exec -y/--yes` package selector에 정확히 결합되는 causal pair를 `github-actions-runtime-package-without-integrity`(HIGH, CWE-829)로 탐지합니다. YAML field 사이 주석, block-scalar 빈 줄과 heredoc 상태를 추적하며, heredoc 본문은 동일 step에서 정확한 출력 target을 `chmod`로 실행 가능하게 만든 경우에만 executable wrapper 증거로 인정합니다. 다른 step 참조, 일반 인자 사용, 변수 prefix 충돌, local tool/tarball, 주석·출력·문서 heredoc은 제외합니다.
- 리포트 출력 하드닝 — 생성된 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
63 changes: 62 additions & 1 deletion docs/TRACEABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
| 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 |
| privileged `pull_request_target` execution of mutable PR-head code | built-in `github-actions-pull-request-target-untrusted-head-execution` rule | Proposed: issue #132 / `.github` PR #635 regression, job-local causal analyzer; promote only after protected merge |
| GitHub Actions versioned registry package bound to npm auto-install | built-in `github-actions-runtime-package-without-integrity` rule | Proposed: bound-package positive and local/unbound/example negative corpus; promote only after protected merge |
| 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
Expand Down Expand Up @@ -47,10 +49,69 @@ For stored webhook/callback SSRF, trace separately:

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.

## Runtime package integrity boundary

The GitHub Actions detector uses a deterministic step/run analyzer rather than a
proximity regex. It is intentionally bounded to one step whose `env` mapping
assigns a versioned registry identity to `*_PACKAGE` and whose immediately
following `run` field places the exact same variable in a supported `npx` or
`npm exec` package-selector position with `-y`/`--yes`. Variable-name boundaries
are exact. Expanded `env:` and compact `- env:` step forms are equivalent, and
same-indent YAML comments do not terminate the step mapping. Blank block-scalar
lines do not end analysis. Heredoc bodies are shell
data and are skipped; analysis resumes after the delimiter. An unquoted heredoc
body counts only when it is redirected to a target and the same step later makes
that exact target executable with `chmod`. This is evidence that auto-install is
admitted, not proof that a download occurred.
Cross-step references, variables used only as ordinary arguments, prefix-colliding
names, unbound local tool names, local tarballs, comments, printed/quoted-heredoc
examples, and non-workflow YAML remain clean. Direct literal package specs, command
prefixes, quoted YAML `run` keys, wider option/order variants, multiple package
variables in one environment mapping, other package managers, composite actions,
reusable workflows, chaining, and indirect expansion remain outside this bounded
analyzer.
The current repository incident remains open until a canonical protected workflow
or immutable released tool contract can scan PR source without trusting PR-owned
bootstrap files.

## Privileged PR-head execution boundary

The GitHub Actions trust-boundary analyzer requires a `pull_request_target`
workflow and evaluates each direct child job independently. A job is in scope
when explicit write authority or a referenced repository secret is present, the
job checks out or shell-materializes
`github.event.pull_request.head.sha`/`ref` or `github.head_ref`, and a later step
executes a repository-local action, script, test, or build command from the
selected tree. One-hop workflow-, job-, and same-step block/flow (including multiline
flow) `env` bindings of the
event head are followed into the same shell command segment as checkout/switch,
or from the most recent `FETCH_HEAD`-writing PR-head fetch into `FETCH_HEAD`
worktree creation and execution linked by normalized `cd`, `pushd`, exact step
`working-directory`, or executable/script path
commands so the retained fixture reproduces the issue #132 CodeGraph execution
shape after the prevention fix merged in ContextualWisdomLab/.github PR #635.
Non-overwriting `--no-write-fetch-head`/`--append` fetches (including bundled short
options) preserve that provenance; a later directly executed checkout/switch that
selects a different tree, or an overwriting fetch, invalidates it. Path restore,
current-HEAD branch creation, inert command text, repeated PR-head selection, and
interpreter options retain their actual provenance semantics.

Metadata-only `pull_request_target` handling, ordinary `pull_request`, trusted
base-SHA checkout, inert output, privilege located only in another job, and a
conservatively recognized scalar or block job-level guard whose every disjunct
contains a standalone atom excluding PR-target execution are negative fixtures.
Top-level mapping, sequence, inline trigger forms, field ordering, and trailing
YAML comments are equivalent; YAML-looking action inputs, comments, heredocs, and run
data are not promoted to authority. A same-repository head predicate is
deliberately not an exclusion. The first bounded analyzer does not claim
complete coverage of reusable/composite action internals, cross-job artifacts,
API-downloaded source archives, cross-step or multi-hop environment aliases, container
entrypoints, or third-party actions that fetch contributor content internally.

## 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.
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.
Loading
Loading