Repository navigation
feat: enforce Cloud-managed Jev and hybrid policies, scoped to agents #873
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
chhhee10
wants to merge
41
commits into
main
Choose a base branch
from
feat/cloud-jev-policies
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+22,617
−532
Open
Changes from all commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
2b322d5
daemon: materialise Cloud Jev policies and report policy errors
chhhee10 4dbb60d
cli: Cloud Jev policies, Cloud Jev mode, and errors.json
chhhee10 aa6f4e4
fp: publish Jev/both policies, deploy --jev-mode, show policy errors
chhhee10 8bc2a91
daemon: a disconnect sticks; transport blips and local paths stay off…
chhhee10 6533bde
cli: bind a both policy to its own Jev half, Cloud-first budget, repo…
chhhee10 dc792f7
fp: `fleet jev-mode`, mode-only deploys, rollback/history show the Je…
chhhee10 8b94626
test: pin the handler's one gated dynamic import of the Jev resolver
chhhee10 ee984f8
cli: an observe both whose Jev half was withheld registers hard witho…
chhhee10 3e9cd78
daemon: don't re-send CLI policy errors the active deployment no long…
chhhee10 c1ddc8e
cli: an observed both counts as hard in the reviewable survey, whatev…
chhhee10 044fe83
test: give "collects every kind of problem" a consistent input
chhhee10 30da857
cli, daemon: redact an absolute path that follows `:` too
chhhee10 f3b5a0b
daemon: never run the OSS cleanup in an overridden cloud policy direc…
chhhee10 5306060
fp: show jevChars, and each policy's share of a Jev budget refusal
chhhee10 4a4b224
daemon: Jev checks no longer reach the machine; keep the Cloud Jev mode
chhhee10 aa49078
cli: Jev checks run on FailproofAI Cloud; call Cloud under a Cloud Je…
chhhee10 018377a
fp: help says Jev checks run on FailproofAI Cloud (C10.6)
chhhee10 a6bb4f8
docs: Cloud Jev checks run on FailproofAI Cloud; CHANGELOG for C10
chhhee10 1f9e8f0
cli: measure the Cloud Jev block cap in UTF-8 bytes
chhhee10 17c186d
cli: Cloud Jev checks ignore a session pause; Cloud gets 5 s to answer
chhhee10 b0e5684
cli: redaction never makes FailproofAI Cloud's target checks laxer (r…
chhhee10 75ff60a
test: replay FailproofAI Cloud's Jev parity fixtures through the curr…
chhhee10 5842f84
cli, daemon: FailproofAI Cloud Jev breaker and health report; name cl…
chhhee10 fa8a559
cli, daemon: review minors on the machine side (m7, m8, n8, n10)
chhhee10 3e2d4f7
cli: a --no-transcripts connect removes a Cloud jev.json (e2e-c10 O2,…
chhhee10 4a7bd35
docs: Cloud Jev waits 5 s, ignores a session pause, and reports rate …
chhhee10 2e966b4
test: pin the intent store's own redaction marker in the cloud block'…
chhhee10 2aa6434
fix(cloud): keep disconnect and Jev outage state fail-narrow
chhhee10 bfde4b5
fix(cloud): preserve shared policy directory on disconnect
chhhee10 9825c0e
test(cloud): isolate managed-policy reader from host config
chhhee10 0f26c6e
feat(enforcement): track agent profiles and enforce scoped Cloud poli…
chhhee10 1020299
fix(enforcement): bind agent scope to the originating hook
chhhee10 cc41294
test(enforcement): replay shared agent target selector cases
chhhee10 2f920d7
docs(policies): explain agent-scoped Cloud deployments
chhhee10 5d8343a
refactor(cloud): validate authority targets once per policy
chhhee10 18b5f11
fix(ipc): require v2 daemon for scoped agent identity
chhhee10 8c22f31
fix(roster): preserve existing profile IDs at capacity
chhhee10 1924794
fix(roster): reclaim unassigned stale profiles at capacity, cap 256
chhhee10 25e445c
chore(osv): ignore braces GHSA-vfj7-8cjw-p6xm (lint tooling, no fix)
chhhee10 5d3fcc5
Merge remote-tracking branch 'origin/main' into feat/cloud-jev-policies
chhhee10 df4f5c9
test(hermes): the fake daemon answers in the client's protocol version
chhhee10 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| // @vitest-environment node | ||
| import { describe, expect, it } from "vitest"; | ||
| import { createHash } from "node:crypto"; | ||
| import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; | ||
| import { join } from "node:path"; | ||
| import { createFixtureEnv } from "../helpers/fixture-env"; | ||
| import { assertAllow, assertPreToolUseDeny, runHook } from "../helpers/hook-runner"; | ||
|
|
||
| describe("real CLI hook distinguishes simultaneous project and user profiles", () => { | ||
| it("applies targeted Cloud JS only when the installed hook carries its actual settings scope", () => { | ||
| const fixture = createFixtureEnv(); | ||
| const projectSettings = join(fixture.cwd, ".claude", "settings.json"); | ||
| const userSettings = join(fixture.home, ".claude", "settings.json"); | ||
| mkdirSync(join(fixture.cwd, ".claude"), { recursive: true }); | ||
| mkdirSync(join(fixture.home, ".claude"), { recursive: true }); | ||
| writeFileSync(projectSettings, '{"hooks":"failproofai --hook PreToolUse --agent-scope project"}'); | ||
| writeFileSync(userSettings, '{"hooks":"failproofai --hook PreToolUse --agent-scope user"}'); | ||
|
|
||
| const fpHome = join(fixture.home, ".failproofai"); | ||
| const rosterDir = join(fpHome, "agents"); | ||
| mkdirSync(rosterDir, { recursive: true }); | ||
| const projectId = "agt_1234567890abcdef"; | ||
| const userId = "agt_abcdef1234567890"; | ||
| const roster = join(rosterDir, "roster.json"); | ||
| writeFileSync(roster, JSON.stringify({ | ||
| schemaVersion: 1, generation: 2, | ||
| agents: [ | ||
| { integration: "claude", instanceId: projectId, settingsPath: projectSettings, | ||
| profileLabel: "project", scope: "project", hookInstalled: true }, | ||
| { integration: "claude", instanceId: userId, settingsPath: userSettings, | ||
| profileLabel: "user", scope: "user", hookInstalled: true }, | ||
| ], | ||
| }), { mode: 0o600 }); | ||
| chmodSync(roster, 0o600); | ||
|
|
||
| const cloudDir = join(fpHome, "policies", "cloud-policies"); | ||
| mkdirSync(join(cloudDir, "artifacts"), { recursive: true }); | ||
| const source = `import { customPolicies, deny } from "failproofai"; | ||
| customPolicies.add({ | ||
| name: "only-project", description: "Only this installation", | ||
| match: { events: ["PreToolUse"] }, | ||
| fn: async () => deny("project agent"), | ||
| });`; | ||
| const digest = createHash("sha256").update(source).digest("hex"); | ||
| const artifact = `artifacts/${digest}.mjs`; | ||
| writeFileSync(join(cloudDir, artifact), source); | ||
| writeFileSync(join(cloudDir, "active.json"), JSON.stringify({ | ||
| schemaVersion: 3, deployment: 1, | ||
| policies: [{ | ||
| id: "scope-check", version: 1, sha256: digest, path: artifact, effect: "enforce", | ||
| agentTargets: [{ integration: "claude", instanceId: projectId }], | ||
| }], | ||
| })); | ||
|
|
||
| const payload = { | ||
| session_id: "scope-test", hook_event_name: "PreToolUse", | ||
| tool_name: "Bash", tool_input: { command: "ls" }, cwd: fixture.cwd, | ||
| }; | ||
| const project = runHook("PreToolUse", payload, { | ||
| homeDir: fixture.home, cwd: fixture.cwd, agentScope: "project", | ||
| }); | ||
| assertPreToolUseDeny(project); | ||
|
|
||
| const user = runHook("PreToolUse", payload, { | ||
| homeDir: fixture.home, cwd: fixture.cwd, agentScope: "user", | ||
| }); | ||
| assertAllow(user); | ||
|
|
||
| const legacyAmbiguous = runHook("PreToolUse", payload, { | ||
| homeDir: fixture.home, cwd: fixture.cwd, | ||
| }); | ||
| assertAllow(legacyAmbiguous); | ||
| const report = JSON.parse(readFileSync(join(cloudDir, "errors.json"), "utf8")); | ||
| expect(report.errors).toContainEqual(expect.objectContaining({ | ||
| id: "agentScope", message: expect.stringContaining("agent_scope_unresolved"), | ||
| })); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "cases": [ | ||
| {"name":"unscoped", "schemaVersion":2, "targets":null, "agent":null, "valid":true, "matches":true}, | ||
| {"name":"integration-all-profiles", "schemaVersion":3, "targets":[{"integration":"claude"}], "agent":{"integration":"claude","instanceId":"agt_1111111111111111"}, "valid":true, "matches":true}, | ||
| {"name":"integration-other-agent", "schemaVersion":3, "targets":[{"integration":"claude"}], "agent":{"integration":"codex","instanceId":"agt_1111111111111111"}, "valid":true, "matches":false}, | ||
| {"name":"profile-exact", "schemaVersion":3, "targets":[{"integration":"hermes","instanceId":"agt_2222222222222222"}], "agent":{"integration":"hermes","instanceId":"agt_2222222222222222"}, "valid":true, "matches":true}, | ||
| {"name":"profile-other-instance", "schemaVersion":3, "targets":[{"integration":"hermes","instanceId":"agt_2222222222222222"}], "agent":{"integration":"hermes","instanceId":"agt_3333333333333333"}, "valid":true, "matches":false}, | ||
| {"name":"profile-other-integration", "schemaVersion":3, "targets":[{"integration":"hermes","instanceId":"agt_2222222222222222"}], "agent":{"integration":"codex","instanceId":"agt_2222222222222222"}, "valid":true, "matches":false}, | ||
| {"name":"scope-unresolved", "schemaVersion":3, "targets":[{"integration":"codex"}], "agent":null, "valid":true, "matches":false}, | ||
| {"name":"or-combination", "schemaVersion":3, "targets":[{"integration":"claude"},{"integration":"hermes","instanceId":"agt_2222222222222222"}], "agent":{"integration":"hermes","instanceId":"agt_2222222222222222"}, "valid":true, "matches":true}, | ||
| {"name":"both-halves-same-scope", "schemaVersion":3, "targets":[{"integration":"codex"}], "agent":{"integration":"codex","instanceId":"agt_4444444444444444"}, "valid":true, "matches":true}, | ||
| {"name":"empty-array", "schemaVersion":3, "targets":[], "agent":null, "valid":false}, | ||
| {"name":"duplicate-selector", "schemaVersion":3, "targets":[{"integration":"codex"},{"integration":"codex"}], "agent":null, "valid":false}, | ||
| {"name":"unknown-integration", "schemaVersion":3, "targets":[{"integration":"invented"}], "agent":null, "valid":false}, | ||
| {"name":"invalid-profile-id", "schemaVersion":3, "targets":[{"integration":"hermes","instanceId":"bad"}], "agent":null, "valid":false}, | ||
| {"name":"scoped-in-schema-two", "schemaVersion":2, "targets":[{"integration":"claude"}], "agent":null, "valid":false} | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # FailproofAI Cloud Jev fixtures | ||
|
|
||
| Shared with the FailproofAI Cloud server (agenteye `server/tests/fixtures/cloud_jev/`). | ||
| **The copies in both repos must be byte-identical.** Nothing in either CI can compare | ||
| them across repos, so each repo pins their sha256 in a test (here | ||
| `__tests__/hooks/cloud-jev-parity.test.ts`, there `server/src/jev_select.rs` | ||
| `fixture_copies_match_the_cli_repo`), and the two pins must match. | ||
|
|
||
| | file | what it pins | | ||
| |---|---| | ||
| | `semantic-valid.json`, `semantic-invalid.json` | Cloud's publish validation of a Jev declaration mirrors `parsePackSemanticPolicy` | | ||
| | `semantic-valid-chars.json` | Cloud's budget count mirrors `questionChars` | | ||
| | `decide-parity.json` | Cloud's port of `scanTargets` + `decide` (v0) / `decideV1` (v1): 2,200 cases | | ||
| | `compile-parity.json` | Cloud's port of `compileRequest`'s per-policy questions and `questionChars` | | ||
| | `select-parity.json` | Cloud's port of `selectPolicies` and the preconditions | | ||
| | `target-scan-cases.json` | the machine's `cloud` block `targetScan` (this repo only) | | ||
|
|
||
| The three `*-parity.json` files are generated by RUNNING this repo's TypeScript: | ||
| `generators/run.sh` (bun; a throwaway `HOME`; deterministic). They are replayed | ||
| through the current code by `__tests__/hooks/cloud-jev-parity.test.ts`, so a | ||
| behaviour change to that path fails a test here. When the change is intended, in | ||
| the same change: | ||
|
|
||
| 1. run `generators/run.sh`; | ||
| 2. copy the three files byte for byte into agenteye `server/tests/fixtures/cloud_jev/`; | ||
| 3. update the sha256 pins in both repos; | ||
| 4. port the change to agenteye `server/src/jev_select.rs`, `jev_decide.rs` and `jev_cloud.rs`. | ||
|
|
||
| Current sha256: | ||
|
|
||
| ``` | ||
| 1b38d72111e8861ee7c8ff0963b32936c57bcd3505ff0ec46722920dba1c0140 decide-parity.json | ||
| a3cf1194ae5b09c8a4ffbbc4515b1a7f4bd27ef317921ff7b788602c0e20c998 compile-parity.json | ||
| 4231c78e435959d989181e2272a3c447af4f4264e31e45a537b9a45a3525a933 select-parity.json | ||
| ``` | ||
|
|
||
| Each file's `source` records the commit and source hashes it was generated from. | ||
| A later comment-only change to those sources does not require regenerating: the | ||
| replay test is the check, not the hashes. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
#!/bin/bash jq -r .version package.json sed -n 1,5p CHANGELOG.mdRepository: FailproofAI/failproofai
Length of output: 232
🏁 Script executed:
Repository: FailproofAI/failproofai
Length of output: 16342
Use today's date for the changelog section.
This PR adds entries under the
2026-09-30heading. The changelog rule requires today's date. Update the heading date.🤖 Prompt for AI Agents
Source: Coding guidelines