Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/policies/osint/alert_budget.rego
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
}
Comment on lines +9 to +12
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The alert_budget_exceeded rule fails to deny if input.collection.alert_count is missing. In OPA, if a variable used in a comparison is undefined, the entire rule is undefined and does not fire. Since the allow rule (lines 14-16) only checks if there are zero deny results, omitting this field effectively bypasses the alert budget enforcement. You should add an explicit check for the presence of alert_count.


allow if {
count(deny) == 0
}
15 changes: 15 additions & 0 deletions .github/policies/osint/alert_budget_test.rego
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
}
11 changes: 11 additions & 0 deletions .github/policies/osint/fixtures/alert_allow_valid.json
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
}
}
11 changes: 11 additions & 0 deletions .github/policies/osint/fixtures/alert_deny_exceeded.json
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
}
}
11 changes: 11 additions & 0 deletions .github/policies/osint/fixtures/alert_deny_unknown_envelope.json
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
}
}
9 changes: 9 additions & 0 deletions .github/policies/osint/fixtures/privacy_allow_valid.json
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"]
}
}
9 changes: 9 additions & 0 deletions .github/policies/osint/fixtures/privacy_deny_pii.json
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"]
}
}
9 changes: 9 additions & 0 deletions .github/policies/osint/fixtures/privacy_deny_retention.json
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"]
}
}
10 changes: 10 additions & 0 deletions .github/policies/osint/fixtures/provenance_allow_valid.json
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
}
}
}
10 changes: 10 additions & 0 deletions .github/policies/osint/fixtures/provenance_deny_escalation.json
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
}
}
}
18 changes: 18 additions & 0 deletions .github/policies/osint/fixtures/tos_allow_valid.json
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"]
}
}
}
18 changes: 18 additions & 0 deletions .github/policies/osint/fixtures/tos_deny_method.json
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"]
}
}
}
13 changes: 13 additions & 0 deletions .github/policies/osint/fixtures/tos_deny_unregistered.json
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": {}
}
30 changes: 30 additions & 0 deletions .github/policies/osint/privacy.rego
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The two deny rules for checking retention_ttl_days can be combined into a single, more concise rule. This improves readability and maintainability. Also, the key retention_ttl_missing is not fully descriptive for the _ <= 0 case. A key like retention_ttl_invalid would be more accurate.

This single rule correctly handles cases where retention_ttl_days is missing, null, zero, or negative.

deny["retention_ttl_invalid"] if {
  not input.collection.retention_ttl_days > 0
}


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
}
15 changes: 15 additions & 0 deletions .github/policies/osint/privacy_test.rego
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
}
21 changes: 21 additions & 0 deletions .github/policies/osint/provenance.rego
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The provenance policy fails to deny if the input.collection.provenance object is missing. All deny rules in this package depend on fields within input.collection.provenance. If the object is missing, none of the deny rules will fire, and the allow rule (lines 19-21) will evaluate to true. This allows bypassing all provenance requirements (artifact IDs, corroboration counts, etc.) by simply omitting the provenance data.


allow if {
count(deny) == 0
}
15 changes: 15 additions & 0 deletions .github/policies/osint/provenance_test.rego
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
}
49 changes: 49 additions & 0 deletions .github/policies/osint/tos.rego
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The ToS policy fails to deny if input.collection.method is missing. The rules method_not_allowed (lines 13-17) and method_not_allowed_by_envelope (lines 19-23) will not fire if method is undefined. If the source and envelope are otherwise valid, the policy will allow the request, bypassing the method allowlist checks. You should add an explicit check to ensure the collection method is specified.


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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The multiple deny["disallowed_tos_method"] rules for hardcoded method names can be consolidated. Using a set of disallowed methods makes the policy more readable and easier to maintain if more methods need to be added in the future.

disallowed_methods := {"circumvent", "credential_share", "paywall_bypass"}

deny["disallowed_tos_method"] if {
  disallowed_methods[input.collection.method]
}


allow if {
count(deny) == 0
}

method_allowed(methods, method) {
methods[_] == method
}
15 changes: 15 additions & 0 deletions .github/policies/osint/tos_test.rego
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
}
38 changes: 38 additions & 0 deletions .github/workflows/osint-governance-verify.yml
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
Loading
Loading