Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
945b149
docs(cedar-hitl): restore and revise HITL gates design, fold adversar…
May 7, 2026
ca76049
feat(cedar-hitl): pin Cedar engines and seed cross-engine parity cont…
May 7, 2026
e32c365
feat(cedar-hitl): three-outcome PolicyEngine core
May 7, 2026
7399f6e
feat(cedar-hitl): approval milestone writers + engine counters
May 7, 2026
4cdeb9f
feat(cedar-hitl): TaskApprovals + AWAITING_APPROVAL transition primit…
May 7, 2026
40c2cd5
feat(cedar-hitl): PreToolUse three-outcome REQUIRE_APPROVAL path
May 7, 2026
78783a9
feat(cedar-hitl): TaskApprovalsTable + SlackUserMapping + status enum
May 7, 2026
ec3b1cc
feat(cedar-hitl): Cedar-wasm layer + wire approval tables into agent …
May 7, 2026
ff74484
feat(cedar-hitl): approve + deny handlers + shared types (§7.1, §7.2)
May 7, 2026
d7fb4fb
feat(cedar-hitl): get-pending + get-policies + link-slack-user handlers
May 7, 2026
697dea9
feat(cedar-hitl): wire Chunk 5 routes + orchestrator + reconciler + a…
May 7, 2026
f05e6cc
feat(cedar-hitl): Chunk 6 CLI — approve / deny / pending / policies
May 7, 2026
faa8722
feat(cedar-hitl): Chunk 7a — persist gate counter + IMPL-23 cache obs…
May 7, 2026
c0dd119
feat(cedar-hitl): Chunk 7b — persist approval_gate_cap from blueprint
May 7, 2026
d4225d6
feat(cedar-hitl): Chunk 7c — observability wrap-up for resolved cap +…
May 8, 2026
91b0483
feat(cedar-hitl): Chunk 8a — extend approval outcome event schema
May 8, 2026
fbcc152
feat(cedar-hitl): Chunk 8b — ApprovalMetricsPublisher + native CloudW…
May 8, 2026
cf90a7e
docs(cedar-hitl): Chunk 9 — sync design doc to Chunks 7b / 8a / 8b
May 8, 2026
349a4d9
feat(cedar-hitl): Chunk 10 review fixes — close 2 blockers + tighten …
May 8, 2026
cea0a16
fix(cedar-hitl): suppress AwsSolutions-IAM5 on Runtime ExecutionRole …
May 8, 2026
fb69894
fix(cedar-hitl): E2E deploy-readiness — policies bundle + onboarding …
May 11, 2026
785037c
fix(cedar-hitl): E2E approval-flow regressions + CLI parity for --app…
May 12, 2026
ed255cf
feat(cedar-hitl): close seven follow-ups from the 2026-05-11/12 E2E pass
May 12, 2026
7019663
chore(cedar-hitl): merge upstream/main — Slack + Linear integrations
May 13, 2026
b06a785
docs(cedar-hitl): add user-facing HITL approval documentation
May 13, 2026
d3791d6
chore(cedar-hitl): merge upstream/main — fanout/SlackNotify refactor …
May 14, 2026
2d11918
fix(cdk): suppress AwsSolutions-COG8 on UserPool
May 15, 2026
77ff903
fix(cedar-hitl): close 3 critical PR-review findings
May 15, 2026
3060316
fix(cedar-hitl): hook + state-machine bugs from PR review
May 15, 2026
7c65704
fix(cedar-hitl): observability + logging gaps from PR review
May 15, 2026
d2e343e
refactor(cedar-hitl): dedup formatMinuteBucket + tighten GetPendingFn…
May 15, 2026
238b23f
refactor(cedar-hitl): type design — discriminated union + Severity al…
May 15, 2026
fe1e64f
chore(ci): add CDK ↔ CLI type sync drift check (S8)
May 15, 2026
e79ac4b
test(cedar-hitl): cover DDB transactions, concurrent decisions, poll …
May 15, 2026
72e614a
chore: post-review style + dependency fixups
May 15, 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
8 changes: 8 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ repos:
exclude: ^docs/node_modules/
stages: [pre-commit]

- id: types-sync-cdk-cli
name: type sync drift (CDK ↔ CLI)
entry: bash -lc 'cd "$(git rev-parse --show-toplevel)" && mise run check:types-sync'
language: system
pass_filenames: false
files: ^(cdk/src/handlers/shared/types\.ts$|cli/src/types\.ts$|scripts/check-types-sync\.ts$)
stages: [pre-commit]

- id: monorepo-security-pre-push
name: security scans (pre-push)
entry: bash -lc 'cd "$(git rev-parse --show-toplevel)" && mise run hooks:pre-push:security'
Expand Down
14 changes: 14 additions & 0 deletions agent/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ COPY --from=gh-builder /out/gh /usr/local/bin/gh
# - build-essential (native compilation for some repos)
# - curl (downloads)
RUN apt-get update && \
# Patch any base-image CVEs that have a fix available in the
# current Debian point release. Without this, transitive system-
# library CVEs (e.g. libnghttp2 CVE-2026-27135) ride the base
# ``python:3.13-slim`` tag until upstream rebuilds, which can be
# weeks. ``--no-install-recommends`` keeps the upgrade narrow and
# reproducible — only already-installed packages get bumped.
apt-get upgrade -y --no-install-recommends && \
apt-get install -y --no-install-recommends \
curl \
git \
Expand Down Expand Up @@ -54,6 +61,13 @@ RUN uv sync --frozen --no-dev --directory /app
# Copy agent code (ARG busts cache so file edits are always picked up)
ARG CACHE_BUST=0
COPY src/ /app/src/
# Cedar HITL built-in policy files (hard_deny.cedar + soft_deny.cedar).
# ``agent/src/policy.py::_POLICIES_DIR`` resolves to ``/app/policies``
# at import time; without these files the PolicyEngine init raises
# ``missing built-in hard-deny policies`` and every task fails at 0
# turns before the agent even connects to the CLI. Discovered during
# Chunk 10 E2E T2.2 — the Dockerfile previously only copied ``src/``.
COPY policies/ /app/policies/
COPY prepare-commit-msg.sh /app/
COPY test_sdk_smoke.py test_subprocess_threading.py /app/

Expand Down
59 changes: 59 additions & 0 deletions agent/policies/hard_deny.cedar
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Built-in hard-deny policy set for Cedar HITL engine.
//
// Hard-deny is ABSOLUTE: no --pre-approve scope and no blueprint `disable:`
// directive can bypass these rules. See docs/design/CEDAR_HITL_GATES.md
// §12.5 and decision #8.
//
// Every rule in this file MUST carry @tier("hard") + @rule_id annotations.
// Adding a rule here expands the set of categorically-forbidden agent
// actions; removing a rule requires a security review.

// Base catch-all permit. Specific forbid rules below override.
@rule_id("base_permit")
permit (principal, action, resource);

// pr_review tasks may never invoke Write. Absolute; cannot be overridden
// by per-blueprint customization or --pre-approve.
@tier("hard")
@rule_id("pr_review_forbid_write")
forbid (
principal == Agent::TaskAgent::"pr_review",
action == Agent::Action::"invoke_tool",
resource == Agent::Tool::"Write"
);

// pr_review tasks may never invoke Edit.
@tier("hard")
@rule_id("pr_review_forbid_edit")
forbid (
principal == Agent::TaskAgent::"pr_review",
action == Agent::Action::"invoke_tool",
resource == Agent::Tool::"Edit"
);

// Reject `rm -rf /` and similar absolute-root destructive commands.
@tier("hard")
@rule_id("rm_slash")
forbid (principal, action == Agent::Action::"execute_bash", resource)
when { context.command like "*rm -rf /*" };

// Reject writes into `.git/` at the repo root (breaks local git state).
@tier("hard")
@rule_id("write_git_internals")
forbid (principal, action == Agent::Action::"write_file", resource)
when { context.file_path like ".git/*" };

// Reject writes into nested `.git/` directories (submodules, worktrees).
@tier("hard")
@rule_id("write_git_internals_nested")
forbid (principal, action == Agent::Action::"write_file", resource)
when { context.file_path like "*/.git/*" };

// Reject any SQL DROP TABLE through Bash — agents should not be running
// destructive DDL against production or dev databases without a human
// in the loop. Hard-deny because even "just testing locally" is a common
// vector for data loss (wrong DB connected via saved credentials).
@tier("hard")
@rule_id("drop_table")
forbid (principal, action == Agent::Action::"execute_bash", resource)
when { context.command like "*DROP TABLE*" };
84 changes: 84 additions & 0 deletions agent/policies/soft_deny.cedar
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Base catch-all permit. Without it, cedarpy's default-deny would turn
// every non-matching Cedar evaluation on this tier into a DENY decision,
// making the soft tier indistinguishable from hard-deny. With it, Cedar
// returns ALLOW (no matching forbid) and our engine's STEP 3 sees only
// the genuine forbid hits as REQUIRE_APPROVAL.
@rule_id("base_permit")
permit (principal, action, resource);

// Built-in soft-deny policy set for Cedar HITL engine.
//
// Soft-deny is the HUMAN-IN-THE-LOOP surface: matching rules pause the
// tool call, write an approval request to DynamoDB, and await a human
// response via `bgagent approve` / `bgagent deny`. See
// docs/design/CEDAR_HITL_GATES.md §§2, 6, 15.4.
//
// Every rule in this file MUST carry:
// @tier("soft")
// @rule_id("...") — stable ID for --pre-approve rule:X
// @approval_timeout_s — integer seconds >= 30 (<120 emits WARN per IMPL-25)
// @severity — "low" | "medium" | "high"
// @category — optional free-form UX grouping
//
// Blueprints may OPT OUT of specific rules here via
// `security.cedarPolicies.disable: [rule_id]`. They may NOT disable any
// rule in hard_deny.cedar (blueprint loader rejects those at task start).

// Gate any git --force / -f push. 300s default approval window, medium severity.
// Covers both long-form (--force) and short-form (-f) variants, including
// the bare `git push -f` invocation with no branch argument.
@tier("soft")
@rule_id("force_push_any")
@approval_timeout_s("300")
@severity("medium")
@category("destructive")
forbid (principal, action == Agent::Action::"execute_bash", resource)
when { context.command like "*git push --force*"
|| context.command like "*git push -f *"
|| context.command like "*git push -f" };

// Force-push to main/prod specifically — longer window, higher severity.
// Multi-match with force_push_any is expected: the engine's annotation
// merging picks min(300, 600)=300s and max(medium, high)=high.
@tier("soft")
@rule_id("force_push_main")
@approval_timeout_s("600")
@severity("high")
@category("destructive")
forbid (principal, action == Agent::Action::"execute_bash", resource)
when { context.command like "*git push --force origin main*"
|| context.command like "*git push --force origin prod*"
|| context.command like "*git push -f origin main*"
|| context.command like "*git push -f origin prod*" };

// Non-force pushes to protected branches — catches the case where an
// agent bypasses PR workflow by pushing directly.
@tier("soft")
@rule_id("push_to_protected_branch")
@approval_timeout_s("300")
@severity("medium")
@category("destructive")
forbid (principal, action == Agent::Action::"execute_bash", resource)
when { context.command like "*git push origin main*"
|| context.command like "*git push origin master*"
|| context.command like "*git push origin prod*"
|| context.command like "*git push origin release/*" };

// Writes to `.env` files typically contain secrets. 600s window, high severity.
@tier("soft")
@rule_id("write_env_files")
@approval_timeout_s("600")
@severity("high")
@category("filesystem")
forbid (principal, action == Agent::Action::"write_file", resource)
when { context.file_path like "*.env" };

// Writes to any path containing "credentials" — SSH keys, AWS creds,
// service-account JSON, etc. 300s window, high severity.
@tier("soft")
@rule_id("write_credentials")
@approval_timeout_s("300")
@severity("high")
@category("auth")
forbid (principal, action == Agent::Action::"write_file", resource)
when { context.file_path like "*credentials*" };
2 changes: 1 addition & 1 deletion agent/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dependencies = [
"uvicorn==0.46.0", #https://pypi.org/project/uvicorn/
"aws-opentelemetry-distro~=0.17.0", #https://pypi.org/project/aws-opentelemetry-distro/
"mcp==1.27.1", #https://pypi.org/project/mcp/
"cedarpy>=4.8.1", #https://github.com/k9securityio/cedar-py
"cedarpy==4.8.0", #https://github.com/k9securityio/cedar-py — EXACT pin per mise.toml parity contract with @cedar-policy/[email protected]
]

[tool.bandit]
Expand Down
8 changes: 8 additions & 0 deletions agent/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ def build_config(
channel_metadata: dict[str, str] | None = None,
trace: bool = False,
user_id: str = "",
approval_timeout_s: int | None = None,
initial_approvals: list[str] | None = None,
initial_approval_gate_count: int = 0,
approval_gate_cap: int | None = None,
) -> TaskConfig:
"""Build and validate configuration from explicit parameters.

Expand Down Expand Up @@ -146,6 +150,10 @@ def build_config(
channel_metadata=channel_metadata or {},
trace=trace,
user_id=user_id,
approval_timeout_s=approval_timeout_s,
initial_approvals=initial_approvals or [],
initial_approval_gate_count=initial_approval_gate_count,
approval_gate_cap=approval_gate_cap,
)


Expand Down
Loading