-
Notifications
You must be signed in to change notification settings - Fork 1
fix(osint): enforce risk-envelope existence and envelope-driven alert budgets in policies #19506
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package osint.alert_budget | ||
|
|
||
| default allow = false | ||
|
|
||
| deny["unknown_envelope"] if { | ||
| not input.risk_envelopes[input.risk_envelope] | ||
| } | ||
|
|
||
| deny["alert_budget_exceeded"] if { | ||
| envelope := input.risk_envelopes[input.risk_envelope] | ||
| input.collection.alert_count > envelope.max_alerts_per_run | ||
| } | ||
|
|
||
| allow if { | ||
| count(deny) == 0 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package osint.alert_budget_test | ||
|
|
||
| test_deny_alert_budget_exceeded if { | ||
| denies := data.osint.alert_budget.deny with input as data.osint.fixtures.alert_deny_exceeded | ||
| count(denies) > 0 | ||
| } | ||
|
|
||
| test_deny_unknown_envelope if { | ||
| denies := data.osint.alert_budget.deny with input as data.osint.fixtures.alert_deny_unknown_envelope | ||
| count(denies) > 0 | ||
| } | ||
|
|
||
| test_allow_alert_budget_valid if { | ||
| data.osint.alert_budget.allow with input as data.osint.fixtures.alert_allow_valid | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "risk_envelope": "balanced", | ||
| "risk_envelopes": { | ||
| "balanced": { | ||
| "max_alerts_per_run": 75 | ||
| } | ||
| }, | ||
| "collection": { | ||
| "alert_count": 50 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "risk_envelope": "conservative", | ||
| "risk_envelopes": { | ||
| "conservative": { | ||
| "max_alerts_per_run": 25 | ||
| } | ||
| }, | ||
| "collection": { | ||
| "alert_count": 30 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "risk_envelope": "unsupported", | ||
| "risk_envelopes": { | ||
| "conservative": { | ||
| "max_alerts_per_run": 25 | ||
| } | ||
| }, | ||
| "collection": { | ||
| "alert_count": 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "collection": { | ||
| "pii_fields": ["email"], | ||
| "redactions": ["email"], | ||
| "retention_ttl_days": 7, | ||
| "never_log_fields": ["tokens"], | ||
| "logged_fields": ["title"] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "collection": { | ||
| "pii_fields": ["email"], | ||
| "redactions": [], | ||
| "retention_ttl_days": 7, | ||
| "never_log_fields": ["tokens"], | ||
| "logged_fields": ["title"] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "collection": { | ||
| "pii_fields": [], | ||
| "redactions": [], | ||
| "retention_ttl_days": 0, | ||
| "never_log_fields": ["tokens"], | ||
| "logged_fields": ["title"] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "collection": { | ||
| "provenance": { | ||
| "artifact_ids": ["A1", "A2"], | ||
| "corroboration_count": 2, | ||
| "single_source": false, | ||
| "escalation": true | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "collection": { | ||
| "provenance": { | ||
| "artifact_ids": ["A1"], | ||
| "corroboration_count": 1, | ||
| "single_source": true, | ||
| "escalation": true | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "collection": { | ||
| "provenance": { | ||
| "artifact_ids": [], | ||
| "corroboration_count": 0, | ||
| "single_source": true, | ||
| "escalation": false | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "collection": { | ||
| "source_id": "summit-news-api", | ||
| "method": "api" | ||
| }, | ||
| "risk_envelope": "conservative", | ||
| "risk_envelopes": { | ||
| "conservative": { | ||
| "allowed_methods": ["api"] | ||
| } | ||
| }, | ||
| "source_registry": { | ||
| "summit-news-api": { | ||
| "allowed_methods": ["api"], | ||
| "disallowed_methods": ["paywall_bypass"] | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "collection": { | ||
| "source_id": "summit-news-api", | ||
| "method": "web_fetch" | ||
| }, | ||
| "risk_envelope": "conservative", | ||
| "risk_envelopes": { | ||
| "conservative": { | ||
| "allowed_methods": ["api"] | ||
| } | ||
| }, | ||
| "source_registry": { | ||
| "summit-news-api": { | ||
| "allowed_methods": ["api"], | ||
| "disallowed_methods": ["paywall_bypass"] | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "collection": { | ||
| "source_id": "unknown-source", | ||
| "method": "api" | ||
| }, | ||
| "risk_envelope": "conservative", | ||
| "risk_envelopes": { | ||
| "conservative": { | ||
| "allowed_methods": ["api"] | ||
| } | ||
| }, | ||
| "source_registry": {} | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package osint.privacy | ||
|
|
||
| default allow = false | ||
|
|
||
| deny["pii_not_redacted"] if { | ||
| pii := input.collection.pii_fields[_] | ||
| not redacted(pii) | ||
| } | ||
|
|
||
| deny["retention_ttl_missing"] if { | ||
| not input.collection.retention_ttl_days | ||
| } | ||
|
|
||
| deny["retention_ttl_missing"] if { | ||
| input.collection.retention_ttl_days <= 0 | ||
| } | ||
|
Comment on lines
+10
to
+16
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The two This single rule correctly handles cases where |
||
|
|
||
| deny["never_log_field_present"] if { | ||
| never_log := input.collection.never_log_fields[_] | ||
| logged := input.collection.logged_fields[_] | ||
| never_log == logged | ||
| } | ||
|
|
||
| allow if { | ||
| count(deny) == 0 | ||
| } | ||
|
|
||
| redacted(field) { | ||
| input.collection.redactions[_] == field | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package osint.privacy_test | ||
|
|
||
| test_deny_missing_redaction if { | ||
| denies := data.osint.privacy.deny with input as data.osint.fixtures.privacy_deny_pii | ||
| count(denies) > 0 | ||
| } | ||
|
|
||
| test_deny_invalid_retention if { | ||
| denies := data.osint.privacy.deny with input as data.osint.fixtures.privacy_deny_retention | ||
| count(denies) > 0 | ||
| } | ||
|
|
||
| test_allow_valid_redaction if { | ||
| data.osint.privacy.allow with input as data.osint.fixtures.privacy_allow_valid | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package osint.provenance | ||
|
|
||
| default allow = false | ||
|
|
||
| deny["missing_artifacts"] if { | ||
| count(input.collection.provenance.artifact_ids) == 0 | ||
| } | ||
|
|
||
| deny["escalation_single_source"] if { | ||
| input.collection.provenance.escalation | ||
| input.collection.provenance.single_source | ||
| } | ||
|
|
||
| deny["insufficient_corroboration"] if { | ||
| input.collection.provenance.escalation | ||
| input.collection.provenance.corroboration_count < 2 | ||
| } | ||
|
Comment on lines
+5
to
+17
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The provenance policy fails to deny if the |
||
|
|
||
| allow if { | ||
| count(deny) == 0 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package osint.provenance_test | ||
|
|
||
| test_deny_missing_artifacts if { | ||
| denies := data.osint.provenance.deny with input as data.osint.fixtures.provenance_deny_missing_artifacts | ||
| count(denies) > 0 | ||
| } | ||
|
|
||
| test_deny_escalation_single_source if { | ||
| denies := data.osint.provenance.deny with input as data.osint.fixtures.provenance_deny_escalation | ||
| count(denies) > 0 | ||
| } | ||
|
|
||
| test_allow_valid_provenance if { | ||
| data.osint.provenance.allow with input as data.osint.fixtures.provenance_allow_valid | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package osint.tos | ||
|
|
||
| default allow = false | ||
|
|
||
| deny["source_not_registered"] if { | ||
| not input.source_registry[input.collection.source_id] | ||
| } | ||
|
|
||
| deny["envelope_missing"] if { | ||
| not input.risk_envelopes[input.risk_envelope] | ||
| } | ||
|
|
||
| deny["method_not_allowed"] if { | ||
| source := input.source_registry[input.collection.source_id] | ||
| method := input.collection.method | ||
| not method_allowed(source.allowed_methods, method) | ||
| } | ||
|
|
||
| deny["method_not_allowed_by_envelope"] if { | ||
| envelope := input.risk_envelopes[input.risk_envelope] | ||
| method := input.collection.method | ||
| not method_allowed(envelope.allowed_methods, method) | ||
| } | ||
|
Comment on lines
+13
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ToS policy fails to deny if |
||
|
|
||
| deny["method_explicitly_disallowed"] if { | ||
| source := input.source_registry[input.collection.source_id] | ||
| method := input.collection.method | ||
| method_allowed(source.disallowed_methods, method) | ||
| } | ||
|
|
||
| deny["disallowed_tos_method"] if { | ||
| input.collection.method == "circumvent" | ||
| } | ||
|
|
||
| deny["disallowed_tos_method"] if { | ||
| input.collection.method == "credential_share" | ||
| } | ||
|
|
||
| deny["disallowed_tos_method"] if { | ||
| input.collection.method == "paywall_bypass" | ||
| } | ||
|
Comment on lines
+31
to
+41
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The multiple |
||
|
|
||
| allow if { | ||
| count(deny) == 0 | ||
| } | ||
|
|
||
| method_allowed(methods, method) { | ||
| methods[_] == method | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package osint.tos_test | ||
|
|
||
| test_deny_unregistered_source if { | ||
| denies := data.osint.tos.deny with input as data.osint.fixtures.tos_deny_unregistered | ||
| count(denies) > 0 | ||
| } | ||
|
|
||
| test_deny_disallowed_method if { | ||
| denies := data.osint.tos.deny with input as data.osint.fixtures.tos_deny_method | ||
| count(denies) > 0 | ||
| } | ||
|
|
||
| test_allow_valid_source if { | ||
| data.osint.tos.allow with input as data.osint.fixtures.tos_allow_valid | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| name: osint-governance-verify | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths: | ||
| - 'config/osint/**' | ||
| - 'schemas/osint/**' | ||
| - '.github/policies/osint/**' | ||
| - 'evidence/**' | ||
| - '.github/workflows/osint-governance-verify.yml' | ||
| workflow_dispatch: | ||
|
|
||
| jobs: | ||
| osint-governance-verify: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup OPA | ||
| uses: open-policy-agent/setup-opa@v2 | ||
| with: | ||
| version: latest | ||
|
|
||
| - name: Run OSINT policy tests | ||
| run: opa test .github/policies/osint | ||
|
|
||
| - name: Schema presence checks | ||
| run: | | ||
| set -euo pipefail | ||
| test -s config/osint/sources.yml | ||
| test -s config/osint/risk_envelopes.yml | ||
| test -s config/osint/retention.yml | ||
| test -s schemas/osint/source_registry.schema.json | ||
| test -s schemas/osint/risk_envelopes.schema.json | ||
| test -s schemas/osint/provenance_receipt.schema.json | ||
| test -s schemas/osint/collection_event.schema.json | ||
| test -s schemas/osint/redaction_report.schema.json |
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.
The
alert_budget_exceededrule fails to deny ifinput.collection.alert_countis missing. In OPA, if a variable used in a comparison is undefined, the entire rule is undefined and does not fire. Since theallowrule (lines 14-16) only checks if there are zerodenyresults, omitting this field effectively bypasses the alert budget enforcement. You should add an explicit check for the presence ofalert_count.