@probelabs/visor 0.1.129 → 0.1.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/cli-main.d.ts.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/docs/author-permissions.md +20 -0
- package/dist/docs/enterprise-policy.md +1325 -0
- package/dist/docs/index.md +10 -0
- package/dist/docs/scheduler-storage.md +433 -0
- package/dist/docs/scheduler.md +12 -2
- package/dist/enterprise/license/validator.d.ts +39 -0
- package/dist/enterprise/license/validator.d.ts.map +1 -0
- package/dist/enterprise/loader.d.ts +25 -0
- package/dist/enterprise/loader.d.ts.map +1 -0
- package/dist/enterprise/policy/opa-compiler.d.ts +37 -0
- package/dist/enterprise/policy/opa-compiler.d.ts.map +1 -0
- package/dist/enterprise/policy/opa-http-evaluator.d.ts +36 -0
- package/dist/enterprise/policy/opa-http-evaluator.d.ts.map +1 -0
- package/dist/enterprise/policy/opa-policy-engine.d.ts +48 -0
- package/dist/enterprise/policy/opa-policy-engine.d.ts.map +1 -0
- package/dist/enterprise/policy/opa-wasm-evaluator.d.ts +34 -0
- package/dist/enterprise/policy/opa-wasm-evaluator.d.ts.map +1 -0
- package/dist/enterprise/policy/policy-input-builder.d.ts +120 -0
- package/dist/enterprise/policy/policy-input-builder.d.ts.map +1 -0
- package/dist/enterprise/scheduler/knex-store.d.ts +41 -0
- package/dist/enterprise/scheduler/knex-store.d.ts.map +1 -0
- package/dist/examples/README.md +23 -0
- package/dist/examples/enterprise-policy/README.md +344 -0
- package/dist/examples/enterprise-policy/policies/capability_resolve.rego +29 -0
- package/dist/examples/enterprise-policy/policies/capability_resolve_test.rego +230 -0
- package/dist/examples/enterprise-policy/policies/check_execute.rego +71 -0
- package/dist/examples/enterprise-policy/policies/check_execute_test.rego +321 -0
- package/dist/examples/enterprise-policy/policies/deploy_production.rego +33 -0
- package/dist/examples/enterprise-policy/policies/deploy_production_test.rego +29 -0
- package/dist/examples/enterprise-policy/policies/slack_channel_gate.rego +17 -0
- package/dist/examples/enterprise-policy/policies/slack_tool_restrict.rego +16 -0
- package/dist/examples/enterprise-policy/policies/tool_invoke.rego +24 -0
- package/dist/examples/enterprise-policy/policies/tool_invoke_test.rego +227 -0
- package/dist/examples/enterprise-policy/visor.yaml +64 -0
- package/dist/failure-condition-evaluator.d.ts +18 -0
- package/dist/failure-condition-evaluator.d.ts.map +1 -1
- package/dist/frontends/slack-frontend.d.ts +1 -0
- package/dist/frontends/slack-frontend.d.ts.map +1 -1
- package/dist/generated/config-schema.d.ts +139 -0
- package/dist/generated/config-schema.d.ts.map +1 -1
- package/dist/index.js +12121 -7169
- package/dist/liquid-extensions.d.ts.map +1 -1
- package/dist/output/traces/{run-2026-02-08T18-16-04-160Z.ndjson → run-2026-02-11T16-20-59-999Z.ndjson} +84 -84
- package/dist/{traces/run-2026-02-08T18-16-51-253Z.ndjson → output/traces/run-2026-02-11T16-21-47-711Z.ndjson} +1032 -1032
- package/dist/policy/default-engine.d.ts +17 -0
- package/dist/policy/default-engine.d.ts.map +1 -0
- package/dist/policy/index.d.ts +4 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/policy-check-command.d.ts +65 -0
- package/dist/policy/policy-check-command.d.ts.map +1 -0
- package/dist/policy/types.d.ts +81 -0
- package/dist/policy/types.d.ts.map +1 -0
- package/dist/providers/ai-check-provider.d.ts.map +1 -1
- package/dist/providers/check-provider.interface.d.ts +2 -0
- package/dist/providers/check-provider.interface.d.ts.map +1 -1
- package/dist/providers/claude-code-check-provider.d.ts.map +1 -1
- package/dist/providers/mcp-check-provider.d.ts.map +1 -1
- package/dist/providers/mcp-custom-sse-server.d.ts.map +1 -1
- package/dist/providers/workflow-check-provider.d.ts.map +1 -1
- package/dist/scheduler/index.d.ts +2 -0
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/schedule-store.d.ts +33 -59
- package/dist/scheduler/schedule-store.d.ts.map +1 -1
- package/dist/scheduler/schedule-tool.d.ts.map +1 -1
- package/dist/scheduler/scheduler.d.ts +24 -3
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/scheduler/store/index.d.ts +7 -0
- package/dist/scheduler/store/index.d.ts.map +1 -0
- package/dist/scheduler/store/json-migrator.d.ts +10 -0
- package/dist/scheduler/store/json-migrator.d.ts.map +1 -0
- package/dist/scheduler/store/sqlite-store.d.ts +32 -0
- package/dist/scheduler/store/sqlite-store.d.ts.map +1 -0
- package/dist/scheduler/store/types.d.ts +127 -0
- package/dist/scheduler/store/types.d.ts.map +1 -0
- package/dist/sdk/check-provider-registry-M3Y6JMTW.mjs +28 -0
- package/dist/sdk/check-provider-registry-PANIXYRB.mjs +28 -0
- package/dist/sdk/{chunk-D5KI4YQ4.mjs → chunk-DIND4ZCV.mjs} +2 -2
- package/dist/sdk/{chunk-DGZPPGJJ.mjs → chunk-EUUAQBTW.mjs} +1463 -568
- package/dist/sdk/chunk-EUUAQBTW.mjs.map +1 -0
- package/dist/sdk/{chunk-XDLQ3UNF.mjs → chunk-GEW6LS32.mjs} +2 -2
- package/dist/sdk/{chunk-N7HO6KKC.mjs → chunk-HOKQOO3G.mjs} +11 -6
- package/dist/sdk/chunk-HOKQOO3G.mjs.map +1 -0
- package/dist/sdk/{chunk-XR7XXGL7.mjs → chunk-JL7JXCET.mjs} +2 -2
- package/dist/sdk/{chunk-6W75IMDC.mjs → chunk-LG4AUKHB.mjs} +2 -2
- package/dist/sdk/{chunk-BDGUM6BA.mjs → chunk-S6CD7GFM.mjs} +1463 -568
- package/dist/sdk/chunk-S6CD7GFM.mjs.map +1 -0
- package/dist/sdk/{chunk-PO7X5XI7.mjs → chunk-SZXICFQ3.mjs} +2 -2
- package/dist/sdk/{chunk-HEX3RL32.mjs → chunk-UCMJJ3IM.mjs} +5 -2
- package/dist/sdk/{chunk-HEX3RL32.mjs.map → chunk-UCMJJ3IM.mjs.map} +1 -1
- package/dist/sdk/{chunk-7YSOINAQ.mjs → chunk-UCNT3PDT.mjs} +342 -5
- package/dist/sdk/chunk-UCNT3PDT.mjs.map +1 -0
- package/dist/sdk/{chunk-R5Z7YWPB.mjs → chunk-V2IV3ILA.mjs} +7 -5
- package/dist/sdk/chunk-V2IV3ILA.mjs.map +1 -0
- package/dist/sdk/{chunk-SGS2VMEL.mjs → chunk-VMLORODQ.mjs} +107 -20
- package/dist/sdk/chunk-VMLORODQ.mjs.map +1 -0
- package/dist/sdk/{chunk-2KB35MB7.mjs → chunk-VPC3QSPW.mjs} +2 -2
- package/dist/sdk/{chunk-J5RGJQ53.mjs → chunk-YJRBN3XS.mjs} +2 -2
- package/dist/sdk/{command-executor-DVVXERLR.mjs → command-executor-TOYBBE7S.mjs} +4 -4
- package/dist/sdk/{config-7VTT64SQ.mjs → config-OGOS4ZU4.mjs} +4 -4
- package/dist/sdk/failure-condition-evaluator-HC3M5377.mjs +17 -0
- package/dist/sdk/{github-frontend-3N2NLO66.mjs → github-frontend-E2KJSC3Y.mjs} +7 -7
- package/dist/sdk/{host-ONVMEHAA.mjs → host-EE6EJ2FM.mjs} +4 -4
- package/dist/sdk/lazy-otel-5NH4ZJJM.mjs +24 -0
- package/dist/sdk/{liquid-extensions-5IZLTFSZ.mjs → liquid-extensions-E4EUOCES.mjs} +5 -5
- package/dist/sdk/memory-store-AAPL2MTE.mjs +12 -0
- package/dist/sdk/{metrics-GXQ2EDXA.mjs → metrics-I6A7IHG4.mjs} +3 -3
- package/dist/sdk/{prompt-state-YHGXB2OA.mjs → prompt-state-VAKKC773.mjs} +4 -4
- package/dist/sdk/{renderer-schema-CMXOLNIG.mjs → renderer-schema-HXEW6BRJ.mjs} +3 -3
- package/dist/sdk/{routing-S3Y7T2X3.mjs → routing-OZQWAGAI.mjs} +9 -8
- package/dist/sdk/schedule-tool-handler-B7TMSG6A.mjs +38 -0
- package/dist/sdk/schedule-tool-handler-IEB2VS7O.mjs +38 -0
- package/dist/sdk/sdk.d.mts +134 -4
- package/dist/sdk/sdk.d.ts +134 -4
- package/dist/sdk/sdk.js +2509 -1085
- package/dist/sdk/sdk.js.map +1 -1
- package/dist/sdk/sdk.mjs +14 -14
- package/dist/sdk/{slack-frontend-R3M2CACB.mjs → slack-frontend-LAY45IBR.mjs} +119 -29
- package/dist/sdk/slack-frontend-LAY45IBR.mjs.map +1 -0
- package/dist/sdk/{trace-helpers-YHNPC7MR.mjs → trace-helpers-PP3YHTAM.mjs} +3 -3
- package/dist/sdk/{tui-frontend-S546M7A7.mjs → tui-frontend-T56PZB67.mjs} +25 -16
- package/dist/sdk/tui-frontend-T56PZB67.mjs.map +1 -0
- package/dist/sdk/workflow-check-provider-2ET3SFZH.mjs +28 -0
- package/dist/sdk/workflow-check-provider-2ET3SFZH.mjs.map +1 -0
- package/dist/sdk/workflow-check-provider-HB4XTD4Z.mjs +28 -0
- package/dist/sdk/workflow-check-provider-HB4XTD4Z.mjs.map +1 -0
- package/dist/sdk/workflow-registry-AAD37XKZ.mjs +12 -0
- package/dist/sdk/workflow-registry-AAD37XKZ.mjs.map +1 -0
- package/dist/slack/client.d.ts +12 -0
- package/dist/slack/client.d.ts.map +1 -1
- package/dist/slack/slack-output-adapter.d.ts.map +1 -1
- package/dist/slack/socket-runner.d.ts.map +1 -1
- package/dist/state-machine/dispatch/execution-invoker.d.ts.map +1 -1
- package/dist/state-machine/dispatch/policy-gate.d.ts +28 -0
- package/dist/state-machine/dispatch/policy-gate.d.ts.map +1 -0
- package/dist/state-machine/states/level-dispatch.d.ts.map +1 -1
- package/dist/state-machine/states/routing.d.ts.map +1 -1
- package/dist/state-machine/states/wave-planning.d.ts.map +1 -1
- package/dist/state-machine-execution-engine.d.ts.map +1 -1
- package/dist/test-runner/core/flow-stage.d.ts.map +1 -1
- package/dist/test-runner/validator.d.ts.map +1 -1
- package/dist/traces/{run-2026-02-08T18-16-04-160Z.ndjson → run-2026-02-11T16-20-59-999Z.ndjson} +84 -84
- package/dist/{output/traces/run-2026-02-08T18-16-51-253Z.ndjson → traces/run-2026-02-11T16-21-47-711Z.ndjson} +1032 -1032
- package/dist/tui/chat-runner.d.ts.map +1 -1
- package/dist/tui/chat-state.d.ts +1 -0
- package/dist/tui/chat-state.d.ts.map +1 -1
- package/dist/tui/chat-tui.d.ts +3 -2
- package/dist/tui/chat-tui.d.ts.map +1 -1
- package/dist/tui/components/chat-box.d.ts +9 -0
- package/dist/tui/components/chat-box.d.ts.map +1 -1
- package/dist/tui/components/input-bar.d.ts +18 -1
- package/dist/tui/components/input-bar.d.ts.map +1 -1
- package/dist/tui/components/status-bar.d.ts +5 -2
- package/dist/tui/components/status-bar.d.ts.map +1 -1
- package/dist/tui/components/trace-viewer.d.ts +1 -0
- package/dist/tui/components/trace-viewer.d.ts.map +1 -1
- package/dist/tui/tui-frontend.d.ts.map +1 -1
- package/dist/types/config.d.ts +107 -3
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/engine.d.ts +5 -0
- package/dist/types/engine.d.ts.map +1 -1
- package/dist/types/execution.d.ts +1 -1
- package/dist/types/execution.d.ts.map +1 -1
- package/package.json +14 -4
- package/dist/sdk/check-provider-registry-ACRGIYOB.mjs +0 -28
- package/dist/sdk/check-provider-registry-VYHKFHK2.mjs +0 -28
- package/dist/sdk/chunk-7YSOINAQ.mjs.map +0 -1
- package/dist/sdk/chunk-BDGUM6BA.mjs.map +0 -1
- package/dist/sdk/chunk-DGZPPGJJ.mjs.map +0 -1
- package/dist/sdk/chunk-N7HO6KKC.mjs.map +0 -1
- package/dist/sdk/chunk-R5Z7YWPB.mjs.map +0 -1
- package/dist/sdk/chunk-SGS2VMEL.mjs.map +0 -1
- package/dist/sdk/failure-condition-evaluator-4WMDF4Q3.mjs +0 -17
- package/dist/sdk/memory-store-3N4AZCYB.mjs +0 -12
- package/dist/sdk/slack-frontend-R3M2CACB.mjs.map +0 -1
- package/dist/sdk/tui-frontend-S546M7A7.mjs.map +0 -1
- package/dist/sdk/workflow-check-provider-4F3432ZP.mjs +0 -28
- package/dist/sdk/workflow-check-provider-A44PBPG2.mjs +0 -28
- package/dist/sdk/workflow-registry-ZAYYXLEP.mjs +0 -12
- /package/dist/sdk/{check-provider-registry-ACRGIYOB.mjs.map → check-provider-registry-M3Y6JMTW.mjs.map} +0 -0
- /package/dist/sdk/{check-provider-registry-VYHKFHK2.mjs.map → check-provider-registry-PANIXYRB.mjs.map} +0 -0
- /package/dist/sdk/{chunk-D5KI4YQ4.mjs.map → chunk-DIND4ZCV.mjs.map} +0 -0
- /package/dist/sdk/{chunk-XDLQ3UNF.mjs.map → chunk-GEW6LS32.mjs.map} +0 -0
- /package/dist/sdk/{chunk-XR7XXGL7.mjs.map → chunk-JL7JXCET.mjs.map} +0 -0
- /package/dist/sdk/{chunk-6W75IMDC.mjs.map → chunk-LG4AUKHB.mjs.map} +0 -0
- /package/dist/sdk/{chunk-PO7X5XI7.mjs.map → chunk-SZXICFQ3.mjs.map} +0 -0
- /package/dist/sdk/{chunk-2KB35MB7.mjs.map → chunk-VPC3QSPW.mjs.map} +0 -0
- /package/dist/sdk/{chunk-J5RGJQ53.mjs.map → chunk-YJRBN3XS.mjs.map} +0 -0
- /package/dist/sdk/{command-executor-DVVXERLR.mjs.map → command-executor-TOYBBE7S.mjs.map} +0 -0
- /package/dist/sdk/{config-7VTT64SQ.mjs.map → config-OGOS4ZU4.mjs.map} +0 -0
- /package/dist/sdk/{failure-condition-evaluator-4WMDF4Q3.mjs.map → failure-condition-evaluator-HC3M5377.mjs.map} +0 -0
- /package/dist/sdk/{github-frontend-3N2NLO66.mjs.map → github-frontend-E2KJSC3Y.mjs.map} +0 -0
- /package/dist/sdk/{host-ONVMEHAA.mjs.map → host-EE6EJ2FM.mjs.map} +0 -0
- /package/dist/sdk/{liquid-extensions-5IZLTFSZ.mjs.map → lazy-otel-5NH4ZJJM.mjs.map} +0 -0
- /package/dist/sdk/{memory-store-3N4AZCYB.mjs.map → liquid-extensions-E4EUOCES.mjs.map} +0 -0
- /package/dist/sdk/{metrics-GXQ2EDXA.mjs.map → memory-store-AAPL2MTE.mjs.map} +0 -0
- /package/dist/sdk/{prompt-state-YHGXB2OA.mjs.map → metrics-I6A7IHG4.mjs.map} +0 -0
- /package/dist/sdk/{routing-S3Y7T2X3.mjs.map → prompt-state-VAKKC773.mjs.map} +0 -0
- /package/dist/sdk/{renderer-schema-CMXOLNIG.mjs.map → renderer-schema-HXEW6BRJ.mjs.map} +0 -0
- /package/dist/sdk/{trace-helpers-YHNPC7MR.mjs.map → routing-OZQWAGAI.mjs.map} +0 -0
- /package/dist/sdk/{workflow-check-provider-4F3432ZP.mjs.map → schedule-tool-handler-B7TMSG6A.mjs.map} +0 -0
- /package/dist/sdk/{workflow-check-provider-A44PBPG2.mjs.map → schedule-tool-handler-IEB2VS7O.mjs.map} +0 -0
- /package/dist/sdk/{workflow-registry-ZAYYXLEP.mjs.map → trace-helpers-PP3YHTAM.mjs.map} +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Tests for AI capability restriction policy
|
|
2
|
+
# Run with: opa test examples/enterprise-policy/policies/
|
|
3
|
+
|
|
4
|
+
package visor.capability.resolve
|
|
5
|
+
|
|
6
|
+
# ---------------------------------------------------------------------------
|
|
7
|
+
# External contributors – allowBash=false and allowEdit=false
|
|
8
|
+
# ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
test_external_gets_allowBash_false {
|
|
11
|
+
capabilities["allowBash"] == false with input as {
|
|
12
|
+
"scope": "capability.resolve",
|
|
13
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
14
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
15
|
+
"actor": {"roles": ["external"], "isLocalMode": false}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
test_external_gets_allowEdit_false {
|
|
20
|
+
capabilities["allowEdit"] == false with input as {
|
|
21
|
+
"scope": "capability.resolve",
|
|
22
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
23
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
24
|
+
"actor": {"roles": ["external"], "isLocalMode": false}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test_external_both_restricted {
|
|
29
|
+
caps := capabilities with input as {
|
|
30
|
+
"scope": "capability.resolve",
|
|
31
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
32
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
33
|
+
"actor": {"roles": ["external"], "isLocalMode": false}
|
|
34
|
+
}
|
|
35
|
+
caps["allowBash"] == false
|
|
36
|
+
caps["allowEdit"] == false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# Reviewer – not developer/admin so allowEdit=false, not external so no bash restriction
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
test_reviewer_gets_allowEdit_false {
|
|
44
|
+
capabilities["allowEdit"] == false with input as {
|
|
45
|
+
"scope": "capability.resolve",
|
|
46
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
47
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
48
|
+
"actor": {"roles": ["reviewer"], "isLocalMode": false}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test_reviewer_no_allowBash_restriction {
|
|
53
|
+
not capabilities["allowBash"] with input as {
|
|
54
|
+
"scope": "capability.resolve",
|
|
55
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
56
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
57
|
+
"actor": {"roles": ["reviewer"], "isLocalMode": false}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# Developer – keeps allowEdit (is_developer), no bash restriction
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
test_developer_no_allowEdit_restriction {
|
|
66
|
+
not capabilities["allowEdit"] with input as {
|
|
67
|
+
"scope": "capability.resolve",
|
|
68
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
69
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
70
|
+
"actor": {"roles": ["developer"], "isLocalMode": false}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
test_developer_no_allowBash_restriction {
|
|
75
|
+
not capabilities["allowBash"] with input as {
|
|
76
|
+
"scope": "capability.resolve",
|
|
77
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
78
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
79
|
+
"actor": {"roles": ["developer"], "isLocalMode": false}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
test_developer_capabilities_empty {
|
|
84
|
+
# Developer should produce no capability restrictions at all
|
|
85
|
+
count(capabilities) == 0 with input as {
|
|
86
|
+
"scope": "capability.resolve",
|
|
87
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
88
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
89
|
+
"actor": {"roles": ["developer"], "isLocalMode": false}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# Admin – keeps everything (is_admin bypasses allowEdit restriction)
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
test_admin_no_allowEdit_restriction {
|
|
98
|
+
not capabilities["allowEdit"] with input as {
|
|
99
|
+
"scope": "capability.resolve",
|
|
100
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
101
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
102
|
+
"actor": {"roles": ["admin"], "isLocalMode": false}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
test_admin_no_allowBash_restriction {
|
|
107
|
+
not capabilities["allowBash"] with input as {
|
|
108
|
+
"scope": "capability.resolve",
|
|
109
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
110
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
111
|
+
"actor": {"roles": ["admin"], "isLocalMode": false}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
test_admin_capabilities_empty {
|
|
116
|
+
count(capabilities) == 0 with input as {
|
|
117
|
+
"scope": "capability.resolve",
|
|
118
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
119
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
120
|
+
"actor": {"roles": ["admin"], "isLocalMode": false}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
# Empty roles – not developer, not admin => allowEdit=false; not external => no bash
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
test_empty_roles_gets_allowEdit_false {
|
|
129
|
+
capabilities["allowEdit"] == false with input as {
|
|
130
|
+
"scope": "capability.resolve",
|
|
131
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
132
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
133
|
+
"actor": {"roles": [], "isLocalMode": false}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
test_empty_roles_no_allowBash_restriction {
|
|
138
|
+
not capabilities["allowBash"] with input as {
|
|
139
|
+
"scope": "capability.resolve",
|
|
140
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
141
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
142
|
+
"actor": {"roles": [], "isLocalMode": false}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# ---------------------------------------------------------------------------
|
|
147
|
+
# Helper rules
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
test_is_developer_true {
|
|
151
|
+
is_developer with input as {
|
|
152
|
+
"actor": {"roles": ["developer"], "isLocalMode": false}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
test_is_developer_false_for_reviewer {
|
|
157
|
+
not is_developer with input as {
|
|
158
|
+
"actor": {"roles": ["reviewer"], "isLocalMode": false}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
test_is_admin_true {
|
|
163
|
+
is_admin with input as {
|
|
164
|
+
"actor": {"roles": ["admin"], "isLocalMode": false}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
test_is_admin_false_for_developer {
|
|
169
|
+
not is_admin with input as {
|
|
170
|
+
"actor": {"roles": ["developer"], "isLocalMode": false}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
test_is_admin_false_for_empty_roles {
|
|
175
|
+
not is_admin with input as {
|
|
176
|
+
"actor": {"roles": [], "isLocalMode": false}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# ---------------------------------------------------------------------------
|
|
181
|
+
# Multi-role actors
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
test_developer_and_external_keeps_edit_but_loses_bash {
|
|
185
|
+
# developer satisfies is_developer so allowEdit is not restricted
|
|
186
|
+
# external triggers allowBash=false
|
|
187
|
+
caps := capabilities with input as {
|
|
188
|
+
"scope": "capability.resolve",
|
|
189
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
190
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
191
|
+
"actor": {"roles": ["developer", "external"], "isLocalMode": false}
|
|
192
|
+
}
|
|
193
|
+
not caps["allowEdit"]
|
|
194
|
+
caps["allowBash"] == false
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
test_admin_and_external_keeps_edit_but_loses_bash {
|
|
198
|
+
# admin satisfies is_admin so allowEdit is not restricted
|
|
199
|
+
# external role still triggers allowBash=false
|
|
200
|
+
caps := capabilities with input as {
|
|
201
|
+
"scope": "capability.resolve",
|
|
202
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
203
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
204
|
+
"actor": {"roles": ["admin", "external"], "isLocalMode": false}
|
|
205
|
+
}
|
|
206
|
+
not caps["allowEdit"]
|
|
207
|
+
caps["allowBash"] == false
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
# Unknown role – not developer/admin => allowEdit=false, not external => no bash
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
test_unknown_role_gets_allowEdit_false {
|
|
215
|
+
capabilities["allowEdit"] == false with input as {
|
|
216
|
+
"scope": "capability.resolve",
|
|
217
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
218
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
219
|
+
"actor": {"roles": ["guest"], "isLocalMode": false}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
test_unknown_role_no_allowBash_restriction {
|
|
224
|
+
not capabilities["allowBash"] with input as {
|
|
225
|
+
"scope": "capability.resolve",
|
|
226
|
+
"check": {"id": "ai-review", "type": "ai"},
|
|
227
|
+
"capability": {"allowEdit": true, "allowBash": true},
|
|
228
|
+
"actor": {"roles": ["guest"], "isLocalMode": false}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Check execution gating policy (Visor Enterprise Edition)
|
|
2
|
+
# Controls which roles can run each check.
|
|
3
|
+
# Contact hello@probelabs.com for licensing.
|
|
4
|
+
|
|
5
|
+
package visor.check.execute
|
|
6
|
+
|
|
7
|
+
default allowed = false
|
|
8
|
+
|
|
9
|
+
# Explicit deny list from YAML policy.deny — if any of the actor's roles
|
|
10
|
+
# appear in the deny list, the check is unconditionally blocked.
|
|
11
|
+
# This is WASM-safe: uses explicit iteration with `some` instead of
|
|
12
|
+
# `not collection[_] == value`.
|
|
13
|
+
denied {
|
|
14
|
+
input.check.policy
|
|
15
|
+
some i, j
|
|
16
|
+
input.check.policy.deny[i] == input.actor.roles[j]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Admin can run anything (unless explicitly denied)
|
|
20
|
+
allowed {
|
|
21
|
+
not denied
|
|
22
|
+
input.actor.roles[_] == "admin"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Developers can run non-production checks (unless explicitly denied)
|
|
26
|
+
allowed {
|
|
27
|
+
not denied
|
|
28
|
+
input.actor.roles[_] == "developer"
|
|
29
|
+
not startswith(input.check.id, "deploy-production")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Reviewers can run read-only checks (info/policy criticality)
|
|
33
|
+
allowed {
|
|
34
|
+
not denied
|
|
35
|
+
input.actor.roles[_] == "reviewer"
|
|
36
|
+
input.check.criticality == "info"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
allowed {
|
|
40
|
+
not denied
|
|
41
|
+
input.actor.roles[_] == "reviewer"
|
|
42
|
+
input.check.criticality == "policy"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Per-step role requirement (from YAML policy.require)
|
|
46
|
+
allowed {
|
|
47
|
+
not denied
|
|
48
|
+
required := input.check.policy.require
|
|
49
|
+
is_string(required)
|
|
50
|
+
input.actor.roles[_] == required
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
allowed {
|
|
54
|
+
not denied
|
|
55
|
+
required := input.check.policy.require
|
|
56
|
+
is_array(required)
|
|
57
|
+
required[_] == input.actor.roles[_]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Local mode bypasses policy for checks without explicit requirements.
|
|
61
|
+
# Checks that declare `policy.require` in YAML still enforce roles even when
|
|
62
|
+
# running locally, so sensitive steps (e.g. deploy-production) stay protected.
|
|
63
|
+
# Explicit deny lists are still enforced in local mode.
|
|
64
|
+
allowed {
|
|
65
|
+
not denied
|
|
66
|
+
input.actor.isLocalMode == true
|
|
67
|
+
not input.check.policy
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
reason = "role is in the deny list for this check" { denied }
|
|
71
|
+
reason = "insufficient role for this check" { not denied; not allowed }
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Tests for check execution gating policy
|
|
2
|
+
# Run with: opa test examples/enterprise-policy/policies/
|
|
3
|
+
|
|
4
|
+
package visor.check.execute
|
|
5
|
+
|
|
6
|
+
# ---------------------------------------------------------------------------
|
|
7
|
+
# Admin bypass – admin can run anything regardless of check id or type
|
|
8
|
+
# ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
test_admin_allowed_for_production_deploy {
|
|
11
|
+
allowed with input as {
|
|
12
|
+
"scope": "check.execute",
|
|
13
|
+
"check": {"id": "deploy-production", "type": "command"},
|
|
14
|
+
"actor": {"roles": ["admin"], "isLocalMode": false}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test_admin_allowed_for_arbitrary_check {
|
|
19
|
+
allowed with input as {
|
|
20
|
+
"scope": "check.execute",
|
|
21
|
+
"check": {"id": "lint-code", "type": "ai", "criticality": "high"},
|
|
22
|
+
"actor": {"roles": ["admin"], "isLocalMode": false}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test_admin_allowed_even_with_require_other_role {
|
|
27
|
+
allowed with input as {
|
|
28
|
+
"scope": "check.execute",
|
|
29
|
+
"check": {"id": "deploy-production", "type": "command", "policy": {"require": "superadmin"}},
|
|
30
|
+
"actor": {"roles": ["admin"], "isLocalMode": false}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Developer restrictions – allowed for non-production, denied for production
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
test_developer_allowed_for_non_production_check {
|
|
39
|
+
allowed with input as {
|
|
40
|
+
"scope": "check.execute",
|
|
41
|
+
"check": {"id": "lint-code", "type": "ai"},
|
|
42
|
+
"actor": {"roles": ["developer"], "isLocalMode": false}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
test_developer_allowed_for_test_suite {
|
|
47
|
+
allowed with input as {
|
|
48
|
+
"scope": "check.execute",
|
|
49
|
+
"check": {"id": "run-tests", "type": "command"},
|
|
50
|
+
"actor": {"roles": ["developer"], "isLocalMode": false}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
test_developer_denied_for_deploy_production {
|
|
55
|
+
not allowed with input as {
|
|
56
|
+
"scope": "check.execute",
|
|
57
|
+
"check": {"id": "deploy-production", "type": "command"},
|
|
58
|
+
"actor": {"roles": ["developer"], "isLocalMode": false}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
test_developer_denied_for_deploy_production_with_suffix {
|
|
63
|
+
not allowed with input as {
|
|
64
|
+
"scope": "check.execute",
|
|
65
|
+
"check": {"id": "deploy-production-canary", "type": "command"},
|
|
66
|
+
"actor": {"roles": ["developer"], "isLocalMode": false}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
# Reviewer – allowed only for info and policy criticality
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
test_reviewer_allowed_for_info_criticality {
|
|
75
|
+
allowed with input as {
|
|
76
|
+
"scope": "check.execute",
|
|
77
|
+
"check": {"id": "info-check", "type": "ai", "criticality": "info"},
|
|
78
|
+
"actor": {"roles": ["reviewer"], "isLocalMode": false}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
test_reviewer_allowed_for_policy_criticality {
|
|
83
|
+
allowed with input as {
|
|
84
|
+
"scope": "check.execute",
|
|
85
|
+
"check": {"id": "policy-check", "type": "ai", "criticality": "policy"},
|
|
86
|
+
"actor": {"roles": ["reviewer"], "isLocalMode": false}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
test_reviewer_denied_for_high_criticality {
|
|
91
|
+
not allowed with input as {
|
|
92
|
+
"scope": "check.execute",
|
|
93
|
+
"check": {"id": "high-check", "type": "ai", "criticality": "high"},
|
|
94
|
+
"actor": {"roles": ["reviewer"], "isLocalMode": false}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
test_reviewer_denied_when_no_criticality {
|
|
99
|
+
not allowed with input as {
|
|
100
|
+
"scope": "check.execute",
|
|
101
|
+
"check": {"id": "some-check", "type": "command"},
|
|
102
|
+
"actor": {"roles": ["reviewer"], "isLocalMode": false}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
test_reviewer_denied_for_critical_criticality {
|
|
107
|
+
not allowed with input as {
|
|
108
|
+
"scope": "check.execute",
|
|
109
|
+
"check": {"id": "critical-check", "type": "command", "criticality": "critical"},
|
|
110
|
+
"actor": {"roles": ["reviewer"], "isLocalMode": false}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
# Per-step policy.require – string match
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
test_require_string_match {
|
|
119
|
+
allowed with input as {
|
|
120
|
+
"scope": "check.execute",
|
|
121
|
+
"check": {"id": "custom-step", "type": "command", "policy": {"require": "developer"}},
|
|
122
|
+
"actor": {"roles": ["developer"], "isLocalMode": false}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
test_require_string_no_match {
|
|
127
|
+
not allowed with input as {
|
|
128
|
+
"scope": "check.execute",
|
|
129
|
+
"check": {"id": "custom-step", "type": "command", "policy": {"require": "admin"}},
|
|
130
|
+
"actor": {"roles": ["external"], "isLocalMode": false}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
test_require_string_exact_role {
|
|
135
|
+
allowed with input as {
|
|
136
|
+
"scope": "check.execute",
|
|
137
|
+
"check": {"id": "ops-step", "type": "command", "policy": {"require": "ops"}},
|
|
138
|
+
"actor": {"roles": ["ops"], "isLocalMode": false}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
# Per-step policy.require – array match
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
test_require_array_match_first_element {
|
|
147
|
+
allowed with input as {
|
|
148
|
+
"scope": "check.execute",
|
|
149
|
+
"check": {"id": "multi-role-step", "type": "command", "policy": {"require": ["admin", "developer"]}},
|
|
150
|
+
"actor": {"roles": ["admin"], "isLocalMode": false}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
test_require_array_match_second_element {
|
|
155
|
+
allowed with input as {
|
|
156
|
+
"scope": "check.execute",
|
|
157
|
+
"check": {"id": "multi-role-step", "type": "command", "policy": {"require": ["admin", "developer"]}},
|
|
158
|
+
"actor": {"roles": ["developer"], "isLocalMode": false}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
test_require_array_no_match {
|
|
163
|
+
not allowed with input as {
|
|
164
|
+
"scope": "check.execute",
|
|
165
|
+
"check": {"id": "multi-role-step", "type": "command", "policy": {"require": ["admin", "developer"]}},
|
|
166
|
+
"actor": {"roles": ["external"], "isLocalMode": false}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
171
|
+
# Local mode – CLI gets broader access regardless of roles
|
|
172
|
+
# ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
test_local_mode_allows_everything {
|
|
175
|
+
allowed with input as {
|
|
176
|
+
"scope": "check.execute",
|
|
177
|
+
"check": {"id": "deploy-production", "type": "command"},
|
|
178
|
+
"actor": {"roles": [], "isLocalMode": true}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
test_local_mode_allows_with_no_roles {
|
|
183
|
+
allowed with input as {
|
|
184
|
+
"scope": "check.execute",
|
|
185
|
+
"check": {"id": "any-check", "type": "ai"},
|
|
186
|
+
"actor": {"roles": [], "isLocalMode": true}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
test_local_mode_false_does_not_grant_access {
|
|
191
|
+
not allowed with input as {
|
|
192
|
+
"scope": "check.execute",
|
|
193
|
+
"check": {"id": "deploy-production", "type": "command"},
|
|
194
|
+
"actor": {"roles": [], "isLocalMode": false}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
# Deny cases – no matching role and not local mode
|
|
200
|
+
# ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
test_external_denied_for_production {
|
|
203
|
+
not allowed with input as {
|
|
204
|
+
"scope": "check.execute",
|
|
205
|
+
"check": {"id": "deploy-production", "type": "command"},
|
|
206
|
+
"actor": {"roles": ["external"], "isLocalMode": false}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
test_empty_roles_denied {
|
|
211
|
+
not allowed with input as {
|
|
212
|
+
"scope": "check.execute",
|
|
213
|
+
"check": {"id": "lint-code", "type": "ai"},
|
|
214
|
+
"actor": {"roles": [], "isLocalMode": false}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
test_unknown_role_denied {
|
|
219
|
+
not allowed with input as {
|
|
220
|
+
"scope": "check.execute",
|
|
221
|
+
"check": {"id": "lint-code", "type": "ai"},
|
|
222
|
+
"actor": {"roles": ["guest"], "isLocalMode": false}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# ---------------------------------------------------------------------------
|
|
227
|
+
# Reason message
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
test_reason_present_when_denied {
|
|
231
|
+
reason == "insufficient role for this check" with input as {
|
|
232
|
+
"scope": "check.execute",
|
|
233
|
+
"check": {"id": "deploy-production", "type": "command"},
|
|
234
|
+
"actor": {"roles": ["external"], "isLocalMode": false}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
test_reason_not_defined_when_allowed {
|
|
239
|
+
not reason with input as {
|
|
240
|
+
"scope": "check.execute",
|
|
241
|
+
"check": {"id": "deploy-production", "type": "command"},
|
|
242
|
+
"actor": {"roles": ["admin"], "isLocalMode": false}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# ---------------------------------------------------------------------------
|
|
247
|
+
# Multi-role actor – at least one matching role grants access
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
test_multi_role_actor_allowed_via_admin {
|
|
251
|
+
allowed with input as {
|
|
252
|
+
"scope": "check.execute",
|
|
253
|
+
"check": {"id": "deploy-production", "type": "command"},
|
|
254
|
+
"actor": {"roles": ["reviewer", "admin"], "isLocalMode": false}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
test_multi_role_actor_developer_plus_reviewer_non_production {
|
|
259
|
+
allowed with input as {
|
|
260
|
+
"scope": "check.execute",
|
|
261
|
+
"check": {"id": "lint-code", "type": "ai"},
|
|
262
|
+
"actor": {"roles": ["developer", "reviewer"], "isLocalMode": false}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
# Deny list tests
|
|
268
|
+
# ---------------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
test_denied_blocks_admin_when_in_deny_list {
|
|
271
|
+
not allowed with input as {
|
|
272
|
+
"actor": {"roles": ["admin"], "isLocalMode": false},
|
|
273
|
+
"check": {"id": "sensitive-check", "type": "ai", "policy": {"deny": ["admin"]}}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
test_denied_blocks_developer_when_in_deny_list {
|
|
278
|
+
not allowed with input as {
|
|
279
|
+
"actor": {"roles": ["developer"], "isLocalMode": false},
|
|
280
|
+
"check": {"id": "some-check", "type": "ai", "policy": {"deny": ["developer"]}}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
test_denied_does_not_block_when_role_not_in_deny_list {
|
|
285
|
+
allowed with input as {
|
|
286
|
+
"actor": {"roles": ["admin"], "isLocalMode": false},
|
|
287
|
+
"check": {"id": "some-check", "type": "ai", "policy": {"deny": ["external"]}}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
test_denied_with_empty_deny_list {
|
|
292
|
+
allowed with input as {
|
|
293
|
+
"actor": {"roles": ["admin"], "isLocalMode": false},
|
|
294
|
+
"check": {"id": "some-check", "type": "ai", "policy": {"deny": []}}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
test_denied_reason_message {
|
|
299
|
+
reason == "role is in the deny list for this check" with input as {
|
|
300
|
+
"actor": {"roles": ["developer"], "isLocalMode": false},
|
|
301
|
+
"check": {"id": "some-check", "type": "ai", "policy": {"deny": ["developer"]}}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
# ---------------------------------------------------------------------------
|
|
306
|
+
# Local mode + policy.require
|
|
307
|
+
# ---------------------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
test_local_mode_with_policy_require_enforces_roles {
|
|
310
|
+
not allowed with input as {
|
|
311
|
+
"actor": {"roles": [], "isLocalMode": true},
|
|
312
|
+
"check": {"id": "secure-deploy", "type": "command", "policy": {"require": "admin"}}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
test_local_mode_with_policy_require_admin_allowed {
|
|
317
|
+
allowed with input as {
|
|
318
|
+
"actor": {"roles": ["admin"], "isLocalMode": true},
|
|
319
|
+
"check": {"id": "secure-deploy", "type": "command", "policy": {"require": "admin"}}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Production deployment policy (Visor Enterprise Edition)
|
|
2
|
+
# Custom rule path example: routes `deploy-production` check to this
|
|
3
|
+
# dedicated package instead of the default `visor.check.execute`.
|
|
4
|
+
#
|
|
5
|
+
# Usage in .visor.yaml:
|
|
6
|
+
# steps:
|
|
7
|
+
# deploy-production:
|
|
8
|
+
# type: command
|
|
9
|
+
# exec: ./deploy.sh production
|
|
10
|
+
# policy:
|
|
11
|
+
# require: admin
|
|
12
|
+
# rule: visor/deploy/production
|
|
13
|
+
#
|
|
14
|
+
# The YAML `rule` field uses slashes; the Rego package uses dots.
|
|
15
|
+
# visor/deploy/production --> package visor.deploy.production
|
|
16
|
+
#
|
|
17
|
+
# Contact hello@probelabs.com for licensing.
|
|
18
|
+
|
|
19
|
+
package visor.deploy.production
|
|
20
|
+
|
|
21
|
+
default allowed = false
|
|
22
|
+
|
|
23
|
+
# Helper: check if actor is an admin (WASM-safe pattern —
|
|
24
|
+
# avoids `not input.actor.roles[_] == "admin"` which is unsafe for WASM)
|
|
25
|
+
is_admin { input.actor.roles[_] == "admin" }
|
|
26
|
+
|
|
27
|
+
# Only admins can deploy to production, and only when targeting main
|
|
28
|
+
allowed {
|
|
29
|
+
is_admin
|
|
30
|
+
input.repository.baseBranch == "main"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
reason = "only admins can deploy to production" { not allowed }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
package visor.deploy.production
|
|
2
|
+
|
|
3
|
+
test_admin_allowed_with_main_branch {
|
|
4
|
+
allowed with input as {
|
|
5
|
+
"actor": {"roles": ["admin"], "isLocalMode": false},
|
|
6
|
+
"repository": {"baseBranch": "main"}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
test_admin_denied_without_main_branch {
|
|
11
|
+
not allowed with input as {
|
|
12
|
+
"actor": {"roles": ["admin"], "isLocalMode": false},
|
|
13
|
+
"repository": {"baseBranch": "develop"}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test_non_admin_denied {
|
|
18
|
+
not allowed with input as {
|
|
19
|
+
"actor": {"roles": ["developer"], "isLocalMode": false},
|
|
20
|
+
"repository": {"baseBranch": "main"}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test_reason_when_denied {
|
|
25
|
+
reason == "only admins can deploy to production" with input as {
|
|
26
|
+
"actor": {"roles": ["developer"], "isLocalMode": false},
|
|
27
|
+
"repository": {"baseBranch": "main"}
|
|
28
|
+
}
|
|
29
|
+
}
|