Skip to content

bug(policy): auto-approval leaves unmergeable proposals pending without an actionable reason #2821

Closed

Description

@krishicks

User Story

As a sandbox developer using automatic policy proposals, I want safe proposals to either merge automatically or explain why they could not, so that I can iterate on a workload without repeatedly diagnosing opaque 403 failures.

Problem Statement

When a host and port already exist as an inspected endpoint for one binary, a denial from another binary can produce a mechanistic proposal for a separate generic L4 endpoint. The proposed endpoint conflicts with the existing endpoint metadata for the same host and port.

The prover can report no new findings while auto-approval subsequently fails during merge or policy validation. The proposal remains pending, but openshell rule get only shows the successful prover result. It does not expose the auto-approval failure. Repeated attempts continue failing and incrementing the proposal hit count.

Impact / Why This Matters

Auto mode appears enabled and the proposal appears safe, but the workload does not progress. Users must inspect gateway logs, infer an endpoint-ambiguity conflict, and manually author a compatible rule. This defeats the quicker deny-propose-approve-retry workflow and is especially costly for dependency resolvers that repeatedly retry the same request.

The current presentation is also misleading: prover: no new findings is shown beside a pending proposal without the separate merge-validation failure that explains why it was not approved.

Acceptance Criteria

  • A mechanistic proposal for a new binary on an existing host and port preserves compatible endpoint metadata or otherwise proposes a mergeable binary expansion without weakening L7 enforcement.
  • With proposal_approval_mode=auto, a mergeable proposal with an empty prover delta and no security notes is approved, persisted as a new base-policy revision, and hot-reloaded.
  • If auto-approval cannot merge or validate a proposal, the failure reason is persisted with the chunk and displayed by openshell rule get.
  • The displayed status distinguishes prover success from merge or policy-validation failure.
  • Repeated observations deduplicate against the existing chunk without hiding the failure reason or creating an indefinite unexplained retry loop.
  • If a later policy revision already covers the pending proposal, the proposal is reconciled into an appropriate terminal or superseded state.
  • Existing L7 endpoint behavior is not silently downgraded to L4 passthrough.

Reproduction Steps

  1. Create repro-policy.yaml with a REST endpoint for one binary:

    version: 1
    
    network_policies:
      cargo_registry:
        name: cargo-registry
        endpoints:
          - host: index.crates.io
            port: 443
            protocol: rest
            enforcement: enforce
            access: read-only
        binaries:
          - path: /usr/bin/cargo
  2. Create a sandbox with the policy and auto approval:

    openshell sandbox create \
      --policy repro-policy.yaml \
      --approval-mode auto \
      auto-approval-repro \
      -- sleep infinity
    
    openshell settings set auto-approval-repro \
      --key agent_policy_proposals_enabled \
      --value true
  3. From a different binary, request the same host and port:

    openshell sandbox exec auto-approval-repro -- \
      /usr/bin/curl https://index.crates.io/config.json
  4. Wait for policy analysis to flush, then inspect pending rules:

    sleep 15
    openshell rule get auto-approval-repro --status pending
  5. Observe a pending L4 proposal for /usr/bin/curl on index.crates.io:443 with prover: no new findings. The request remains denied. Gateway logs contain an auto-approval failure, but the chunk output does not contain its reason.

Environment

  • OpenShell CLI: 0.0.83
  • Host OS: macOS Darwin 25.5.0, ARM64
  • Compute runtime: Podman
  • Policy advisor: enabled at sandbox scope
  • Proposal approval mode: auto at sandbox scope
  • Providers v2: enabled globally

Logs

Status: pending
Rule: allow_index_crates_io_443
Binary: /usr/bin/curl
Validation: prover: no new findings
Endpoints: index.crates.io:443 [L4]

[gateway] WARN auto-approval failed; chunk remains pending for human review

Activity

  1. zredlined commented on Aug 20, 2026

    @zredlined
    Collaborator

    Related follow-up: #2841. Our long-horizon Policy Advisor experiments reproduced this failure class repeatedly and exposed the broader cause: proposal review and application do not evaluate the same complete effective-policy candidate. #2841 proposes validating the exact candidate before review and binding approval to that evaluated candidate. That should prevent the post-review endpoint-ambiguity failure described here. I am leaving this issue open as the concrete regression case and for its separate failure-persistence and presentation requirements; implementation and regression testing can determine whether one change fully resolves both.

  2. johntmyers commented on Aug 20, 2026

    @johntmyers
    Collaborator

    Visual explanation of the three-part failure

    In plain language: OpenShell drafts the wrong kind of change, performs an incomplete safety review that looks successful, then discovers during application that the change is impossible—but only records that discovery in logs.

    flowchart TD
        A["Existing policy<br/>cargo may use index.crates.io:443<br/>through inspected REST, read-only rules"] --> B["curl tries the same address"]
        B --> C["Denied<br/>The rule permits cargo, not curl"]
    
        subgraph P1["Part 1 — The proposal describes the wrong shape"]
            C --> D["Mechanistic mapper sees:<br/>host + port + curl"]
            D --> E["It does not consult the existing policy"]
            E --> F["Proposes a new rule:<br/>curl → generic L4 endpoint<br/>advisor_proposed = true"]
            G["What it should propose:<br/>Add curl to the existing REST rule<br/>and preserve its inspection settings"]
        end
    
        subgraph P2["Part 2 — The initial review gives a misleading green light"]
            F --> H["Gateway tries to combine the proposal<br/>with the existing REST rule"]
            H --> I{"Can it combine them<br/>without unintended changes?"}
            I -->|"No"| J["Safety guard keeps them separate<br/>to avoid silently widening or changing<br/>the existing cargo authorization"]
            J --> K["Candidate now contains two entries<br/>for index.crates.io:443:<br/>① existing REST endpoint<br/>② proposed L4 endpoint"]
            K --> L["Initial checks are incomplete<br/>They do not run the full endpoint-ambiguity validation"]
            L --> M["Prover asks:<br/>Does this introduce a modeled security finding?"]
            M --> N["Answer: no new findings ✅"]
        end
    
        subgraph P3["Part 3 — Applying uses stricter checks, then hides the failure"]
            N --> O["Auto-approval tries to save and hot-reload the policy"]
            O --> P["Full candidate validation finally runs"]
            P --> Q["Collision detected ❌<br/>Same host and port, incompatible provenance:<br/>advisor_proposed = false vs true"]
            Q --> R["Policy is not saved<br/>The original REST policy remains active"]
            R --> S["Failure is written only to gateway logs"]
            S --> T["The proposal stays pending<br/>but the CLI still shows:<br/>prover: no new findings"]
            T --> U["curl retries → another 403<br/>Same proposal is deduplicated<br/>Hit count increases indefinitely"]
        end
    
        V["User's experience:<br/>Auto mode looks enabled and safe,<br/>but nothing progresses and no reason is shown"]:::user
        U --> V
    
        classDef user fill:#7f1d1d,color:#fff,stroke:#ef4444,stroke-width:2px;
        classDef bad fill:#3f1d1d,color:#fff,stroke:#f87171;
        classDef misleading fill:#422006,color:#fff,stroke:#fbbf24;
        classDef normal fill:#172554,color:#fff,stroke:#60a5fa;
    
        class F,J,K bad;
        class L,M,N,T misleading;
        class A,B,C,D,E,H,I,O,P,R,S,U normal;
    
    Loading

    The intended flow is:

    flowchart LR
        A["curl is denied"] --> B["Consult the current effective policy"]
        B --> C["Convert the denial into:<br/>add curl to the existing REST rule"]
        C --> D["Run the complete candidate validation<br/>before showing or auto-approving it"]
        D --> E{"Valid?"}
        E -->|"Yes"| F["Save revision and hot-reload"]
        E -->|"No"| G["Keep pending or reject<br/>and persist the exact application error"]
        F --> H["curl retries through<br/>the existing REST/read-only enforcement"]
        G --> I["CLI shows separate results:<br/>Prover: …<br/>Application: failed because …"]
    
    Loading
  3. johntmyers commented on Aug 20, 2026

    @johntmyers
    Collaborator

    Proposed tracking plan

    Use this issue as the implementation and regression tracking issue. #2841 remains related design context for the broader invariant: proposal review and application must evaluate the same complete effective-policy candidate.

    flowchart TD
        A["Denied request<br/>curl → index.crates.io:443"] --> B["1. Build the canonical candidate"]
    
        B --> C{"Compatible endpoint<br/>already exists?"}
        C -->|"No"| D["Create a separate narrow rule"]
        C -->|"Yes"| E{"Who owns the<br/>existing rule?"}
    
        E -->|"Sandbox policy"| F["Create a binary expansion<br/>Add curl to the existing REST rule<br/>Preserve L7, access, TLS, and provenance"]
        E -->|"Provider policy"| G["Treat provider rule as immutable<br/>Read its contract, but create a compatible<br/>sandbox-owned overlay for curl"]
    
        D --> H["2. Evaluate the complete effective candidate"]
        F --> H
        G --> H
    
        H --> I["Merge and structural validation"]
        I --> J["Endpoint, L7, middleware,<br/>provider, and credential validation"]
        J --> K["Run the prover on that exact valid candidate"]
    
        K --> L{"Candidate valid and<br/>prover gate satisfied?"}
    
        L -->|"No"| M["Return and persist the exact reason<br/>Keep prover and application results separate"]
        L -->|"Yes"| N["3. Store the evaluation hash/token<br/>and its validation + prover result<br/>Review or auto-approve this exact candidate"]
    
        N --> O["Before applying, rebuild from live inputs<br/>and recompute the evaluation hash"]
        O --> P{"Evaluation hash<br/>still matches?"}
    
        P -->|"No"| Q["Inputs changed<br/>Run complete validation + prover again"]
        Q --> L
    
        P -->|"Yes"| R["Reuse the stored validation + prover result<br/>Do not rerun the prover"]
        R --> S["Atomically persist revision and hot-reload"]
    
        S --> T["4. Reconcile pending proposals<br/>Supersede anything now covered"]
        T --> U["Retry succeeds through the retained<br/>REST/read-only enforcement"]
    
        M --> V["CLI, TUI, and policy.local show:<br/>Prover: …<br/>Application: failed because …"]
    
    Loading

    Implementation slices

    1. Canonical proposal construction

      • Add a shared policy helper that prepares a proposal against the current effective policy.
      • For the reproduction in this issue, produce “add /usr/bin/curl to the existing REST rule,” not a second L4 endpoint.
      • Preserve the existing endpoint's inspection and access contract and keep advisor provenance scoped to the new binary.
      • Treat provider rules as immutable inputs. Never fold a proposal into or mutate a _provider_* rule. When a provider endpoint supplies the compatible contract, create a sandbox-owned overlay while leaving the provider layer unchanged.
    2. One complete candidate evaluator

      • Replace the partial submit-time check with the same merge, ambiguity, L7, middleware, provider, credential, and prover evaluation used before persistence.
      • Use the evaluator for submission, reviewer context, auto-approval, and human/SDK approval.
      • Bind approval to a deterministic evaluation hash/token. Its inputs must cover the complete effective candidate, provider-layer revisions, credential capability/scope fingerprint, relevant settings, canonical operation, and evaluator/prover version. Do not include credential secret values.
      • Before applying, rebuild from live inputs and recompute only the evaluation hash. When it is unchanged, reuse the stored validation and prover result. When it changes, run the complete evaluator again and require review of the refreshed candidate.
    3. Actionable proposal state

      • Store application/preflight failure separately from validation_result and rejection_reason.
      • Surface it through gRPC, policy.local, CLI, TUI, and generated SDKs.
      • Preserve the latest failure across mechanistic dedup hits and clear it after successful application.
    4. Reconciliation

      • After policy changes and before mechanistic dedup, compare pending chunks with the live effective policy.
      • Mark already-covered chunks terminal/superseded with the active policy revision and notify watchers.
    5. Regression coverage and docs

      • Add the exact cargo/curl auto-approval regression: one retained REST endpoint, a new policy revision, hot reload, and a successful retry without L7 downgrade.
      • Test immutable provider inputs and sandbox-owned overlays, persisted application failures, dedup behavior, unchanged-hash prover reuse, changed-hash fresh review, and later-policy reconciliation.
      • Extend e2e/policy-advisor/mechanistic-smoke.sh and update the policy-advisor architecture and user documentation.

    Completion criteria

    • The reproduction in this issue auto-approves a mergeable binary expansion and hot-reloads it.
    • Provider policy remains immutable throughout proposal construction and application.
    • No proposal is presented as prover-clean unless its complete effective candidate is valid.
    • Approval applies the same candidate that was reviewed, reusing stored prover results when the evaluation hash is unchanged, or reports that inputs changed.
    • Failures are actionable in user-facing proposal status rather than gateway logs alone.
    • Repeated observations cannot leave an already-covered proposal pending indefinitely.
  4. zredlined commented on Aug 20, 2026

    @zredlined
    Collaborator

    This looks solid and covers both the concrete failure and the broader reviewer/application mismatch. Keeping advisor provenance scoped to the new binary while retaining the existing endpoint contract is the right shape. Two implementation details to preserve: treat provider policy rules as immutable inputs and materialize any compatible expansion in the sandbox base policy; and avoid rerunning the prover when the recomputed complete-candidate hash is unchanged and the stored result is still applicable. I'm good using this issue as the implementation and regression tracker and closing #2841 as superseded.

  5. johntmyers commented on Aug 20, 2026

    @johntmyers
    Collaborator

    🏗️ build-plan

    Implementation Plan

    Issue type: fix
    Complexity: High
    Confidence: Medium — the main flow is clear; the remaining uncertainty is the boundary between gateway preflight and runtime-only supervisor validation

    Summary

    Build one gateway-owned proposal pipeline that canonicalizes a denial against the live effective policy, validates the complete candidate, stores that evaluation with an opaque review token, and applies only that same candidate. This fixes the cargo/curl regression and absorbs #2841's review/apply mismatch while preserving provider rules as immutable inputs and reusing prover results whenever the recomputed complete-candidate hash is unchanged.

    flowchart TD
        A["Denial or agent-authored AddRule"] --> B["Read live base policy, immutable provider layers,<br/>credential capabilities, and relevant settings"]
        B --> C["Canonicalize the requested authorization"]
        C --> D["Build base-policy candidate<br/>and complete effective candidate"]
        D --> E["Run merge, structural, endpoint, L7,<br/>middleware, provider, credential, and prover checks"]
        E --> F{"Complete candidate valid?"}
        F -->|"No"| G["Return/store exact application failure<br/>Do not present it as prover-clean"]
        F -->|"Yes"| H["Store candidate context, review token,<br/>validator result, and prover result"]
        H --> I["Human/SDK approval supplies token<br/>or auto mode selects stored evaluation"]
        I --> J["Rebuild from live inputs<br/>and recompute token"]
        J --> K{"Token unchanged?"}
        K -->|"No"| L["FAILED_PRECONDITION<br/>Refresh evaluation and require fresh review"]
        K -->|"Yes"| M["Reuse stored prover result<br/>Do not rerun prover"]
        M --> N["CAS-write the reviewed base-policy revision"]
        N --> O["Hot-reload and reconcile<br/>now-covered pending chunks"]
    
    Loading

    Scope

    • crates/openshell-server/src/grpc/policy.rs: introduce the canonical candidate evaluator and use it for submission, auto approval, single approval, bulk approval, editing, persistence, and post-write reconciliation.
    • crates/openshell-policy/src/merge.rs: add a policy-aware canonicalization helper for compatible binary expansion and sandbox-owned overlays; never mutate or merge into _provider_* rules.
    • crates/openshell-policy/src/lib.rs, crates/openshell-policy/src/l7_validate.rs, and the corresponding supervisor L7 validation call sites: share all deterministic endpoint/L7/middleware semantics needed to reject gateway-known invalid candidates before review; leave runtime/OPA construction as defense in depth.
    • proto/openshell.proto: add candidate/evaluation metadata, a distinct application failure, and review tokens to chunk and approval messages.
    • crates/openshell-server/src/policy_store.rs, crates/openshell-server/src/persistence/sqlite.rs, and crates/openshell-server/src/persistence/postgres.rs: round-trip evaluation state and provide compare-and-set updates without losing status, failure text, or dedup hit counts. The generic protobuf payload means no relational schema migration should be necessary.
    • crates/openshell-cli/src/run.rs and crates/openshell-tui/src/ui/sandbox_draft.rs: show candidate status, prover result, and application failure separately; refetch and submit the current review token on approval.
    • crates/openshell-supervisor-network/src/policy_local.rs: return candidate context/token and distinct application feedback through proposal and /wait payloads.
    • sdk/go/openshell/v1/types/policy.go, sdk/go/openshell/v1/internal/converter/policy.go, sdk/go/openshell/v1/policy_client.go, generated Go protobufs, and generated TypeScript raw bindings: let SDK reviewers read candidate context/token and supply the token for approval. Add a curated TypeScript policy API only if one is introduced independently; today the repository exposes this surface through raw generated bindings.
    • e2e/policy-advisor/, architecture/security-policy.md, docs/sandboxes/policy-advisor.mdx, and relevant CLI skill/reference text: cover the regression and document the new review/apply contract and status output.

    Implementation Steps

    1. Lock the canonical merge behavior with policy tests.

      • Add an openshell-policy helper that takes the proposed AddRule, current sandbox base policy, and composed provider policy and returns a canonical sandbox-base operation.
      • When a compatible sandbox endpoint exists, express the change as a binary expansion that retains its protocol, enforcement, access/rules, deny rules, TLS, credential-signing, and middleware-facing fields.
      • When only a compatible provider endpoint exists, copy that contract into a new sandbox-owned overlay for the denied binary. Provider rules remain read-only composition inputs; the helper must never edit or fold into _provider_*.
      • Reject ambiguous/incompatible ownership rather than synthesizing a generic L4 endpoint or weakening L7 inspection. Keep advisor provenance scoped to the added binary.
    2. Create one complete evaluate_proposal_candidate path in the gateway.

      • Inputs: canonical operation, current base-policy revision/hash, global-override identity if applicable, composed provider layers, credential capability/scope fingerprint, relevant proposal/provider settings, and an evaluator/prover version marker.
      • Outputs: canonical operation, current and candidate effective-policy context (typed policies or a deterministic generic diff), candidate base policy, validation/security results, prover result, and deterministic evaluation hash/review token.
      • Run merge guards, base-policy safety validation, provider composition, endpoint ambiguity, binary declaration, shared L7/middleware semantics, credential binding, and prover infrastructure checks before a proposal becomes reviewable.
      • Move any remaining deterministic supervisor-only L7 checks (including malformed GraphQL shapes from bug(policy): preflight the exact effective candidate before proposal review #2841) into shared typed validation called by both gateway and supervisor. Do not make the server depend on the supervisor crate or instantiate its runtime OPA engine.
    3. Define the hash and atomicity contract.

      • Hash the canonical operation, complete effective candidate, base-policy generation/source, provider-layer fingerprint, credential capability/scope fingerprint, evaluation-affecting settings, and evaluator/prover version. Exclude secret values, timestamps, hit counts, and rationale text.
      • Persist the hash together with the candidate context and exact validation/prover results.
      • Immediately before apply, rebuild from live inputs and recompute the hash without running the prover. If unchanged, reuse the stored result. If changed, return FAILED_PRECONDITION, refresh the stored evaluation, and leave the chunk pending for fresh review.
      • Persist with a compare-and-set on the same base-policy generation and input fingerprints. Do not let apply_merge_operations_with_retry silently rebase a reviewed proposal onto newer inputs; a conflict is stale review, not a retry opportunity.
    4. Add wire and stored proposal state.

      • Add an evaluation/candidate context to PolicyChunk, DraftChunkPayload, and StoredDraftChunk: opaque review_token, current/candidate policy hashes plus a canonical candidate representation/diff, and evaluator version.
      • Add application_error separately from validation_result (prover) and rejection_reason (reviewer decision).
      • Add review_token to ApproveDraftChunkRequest. For ApproveAllDraftChunksRequest, add repeated (chunk_id, review_token) selections so each human-reviewed chunk is bound explicitly; auto approval uses the stored token internally.
      • Treat missing/mismatched tokens as FAILED_PRECONDITION for reviewable chunks, with a refetch instruction. Regenerate committed Go protobufs and ephemeral TypeScript bindings using repository tasks.
    5. Route every proposal transition through the evaluator.

      • Submission: canonicalize and fully evaluate first. Agent-authored deterministic failures return directly to the proposer and never enter the reviewer inbox. Mechanistic failures retain a queryable chunk/application reason so repeated denials stay actionable.
      • Auto approval: apply a clean, unchanged stored candidate without rerunning the prover; persist the new revision and trigger hot reload.
      • Human/SDK single and bulk approval: require matching token(s). Bulk evaluation proceeds deterministically in request order, with each accepted prefix changing the inputs for the next item; stale entries remain pending and are reported, never silently rebound.
      • Edit: clear the old token/results/error, canonicalize and evaluate the edited proposal, then expose a new token.
      • Dedup: increment observations while preserving the latest application failure; reevaluate only when the complete-candidate hash changed.
    6. Persist actionable outcomes and reconcile covered work.

      • Persist deterministic invalid candidates as terminal/rejected with their application error; keep changed-input or transient write/reload failures pending with an actionable error.
      • After every successful policy revision and before mechanistic dedup, compare pending canonical requests with the live effective policy using policy_covers_rule (including binary and endpoint contract, not host/port alone).
      • Atomically transition covered chunks to the existing terminal rejected state with a reason such as covered by active policy revision N, and notify draft/policy.local watchers. A distinct superseded enum can remain follow-up work.
    7. Update user and reviewer surfaces.

      • CLI/TUI output must never summarize a chunk as simply safe when application preflight failed. Present Candidate, Prover, and Application independently.
      • policy.local, Go, and TypeScript expose the exact candidate context/token used for the decision and the refreshed context after a stale-token response.
      • Document that “recompute from live inputs” rebuilds the candidate only to compare its token; the prover is reused when the token matches and rerun only after decision-relevant inputs change.

    Test Plan

    • Unit tests:
      • openshell-policy: compatible cargo REST/read-only binary expansion; no L7 downgrade; provider rule immutability plus sandbox overlay; incompatible contracts rejected; malformed GraphQL/L7 and middleware shapes rejected by the shared validator; canonical coverage behavior.
      • openshell-server: submit uses the complete evaluator; invalid agent proposal bypasses inbox; mechanistic failure persists; unchanged hash reuses an instrumented/counted prover result; changed base/provider/credential/settings/evaluator inputs produce a new token and FAILED_PRECONDITION; CAS conflict cannot apply a different candidate; edit invalidates evaluation; dedup preserves errors; later coverage reconciles terminally.
      • Persistence: SQLite/Postgres protobuf round trips and targeted CAS updates preserve status and hit count.
      • CLI/TUI/policy.local: separate candidate/prover/application rendering, token propagation, stale response, and watcher payloads.
      • Go converters/client and TypeScript raw bindings: candidate/token round trip and token-bearing single/bulk approval.
    • Integration tests: Add the full cargo/curl scenario to the gateway policy harness: existing cargo REST/read-only rule, mechanistic curl denial, auto mode, approved base-policy revision, provider-safe composition, retained L7 contract, and policy_reloaded. Add replay fixtures for endpoint ambiguity, missing binary, and malformed GraphQL preflight failures with the active policy unchanged.
    • E2E tests: Extend e2e/policy-advisor/mechanistic-smoke.sh or add a sibling test that creates the cargo policy, triggers curl, waits for auto approval/hot reload, verifies no unexplained pending chunk remains, retries successfully, and confirms the effective endpoint is still REST/read-only. Run the Docker gateway lane because this changes policy application/hot reload.
    • Verification commands: targeted crate tests while iterating, then mise run pre-commit, mise run test, mise run go:ci, mise run sdk:ts:ci, and the relevant mise run e2e:docker policy-advisor lane before the PR.

    Risks & Open Questions

    • The exact candidate-context wire shape should be settled before implementation: a typed current/candidate effective policy is clearest but larger; a canonical generic diff is smaller but must be proven deterministic and complete enough for reviewers. Either must represent the object bound by the token.
    • Audit the current split between openshell-policy validation and openshell-supervisor-network::l7::validate_l7_policies. Any deterministic rule the gateway can know must move behind a shared validator; runtime/environment-dependent checks remain defense in depth.
    • Bulk approval needs explicit partial-result semantics when an earlier approved chunk makes a later token stale. Recommended behavior is ordered processing with per-chunk approved/stale/invalid results, never all-or-nothing silent rebasing.
    • Adding mandatory review tokens changes approval semantics for older clients despite additive protobuf fields. Return a precise FAILED_PRECONDITION and coordinate CLI/TUI/Go/TypeScript releases in the same change.
    • No new gateway TOML, Helm, or driver setting is expected. No SELinux/AppArmor difference is expected because this does not change process identity, /proc, exec labeling, Landlock, or inter-process visibility.

    Documentation Impact

    • Update architecture/security-policy.md with canonical proposal, evaluation-token, immutable-provider, and reconciliation invariants.
    • Update docs/sandboxes/policy-advisor.mdx with exact-candidate review, stale-review behavior, auto-mode prover reuse, and distinct status fields.
    • Update CLI/agent reference text if it documents openshell rule get or policy.local proposal payloads. docs/reference/gateway-config.mdx is not affected unless implementation unexpectedly adds configuration, which this plan does not recommend.

    Revision 1 — initial plan

  6. johntmyers commented on Aug 20, 2026

    @johntmyers
    Collaborator

    Implementation is ready in #2850.

    The PR implements the tracking plan discussed above:

    • The gateway builds and validates the exact effective-policy candidate before the prover evaluates it.
    • Existing inspected endpoint contracts are preserved; provider rules are immutable inputs and sandbox additions become overlays.
    • Review is bound to a token covering the candidate and its live policy/provider/non-secret credential inputs.
    • If the recomputed token is unchanged, approval reuses the stored prover result. If inputs changed, the refreshed candidate remains pending and requires fresh review.
    • Merge, L7 shape, provider-composition, credential, and prover failures are persisted as actionable application errors.
    • Pending proposals already covered by a successful policy write are reconciled out of the inbox.

    Verification includes the full mise run ci gate, focused Rust/Go/TypeScript tests, the standard Docker E2E, and a new #2821 Docker regression proving that adding /usr/bin/curl to an existing REST/read-only endpoint auto-approves, hot-reloads, and does not downgrade the endpoint to L4.

  7. added 3 commits that reference this issue on Aug 20, 2026
    6b82f88
    0d4d644
    e7504c4
  8. added a commit that references this issue on Aug 21, 2026
    679fe4c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:acceptedA maintainer decided OpenShell should pursue this issuestate:agent-readyApproved for agent implementation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions