Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"plugins": [
{
"name": "rogue",
"version": "1.0.3",
"description": "Rogue Security AIDR real-time AI agent detection and response for OpenAI Codex",
"version": "1.0.4",
"description": "Rogue Security AIDR \u2014 real-time AI agent detection and response for OpenAI Codex",
"author": {
"name": "Rogue Security",
"url": "https://www.rogue.security"
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"plugins": [
{
"name": "rogue",
"version": "1.0.29",
"version": "1.0.30",
"description": "Rogue Security AIDR \u2014 real-time AI agent detection and response for Claude Code",
"author": {
"name": "Rogue Security",
Expand Down
4 changes: 2 additions & 2 deletions .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"plugins": [
{
"name": "rogue-security",
"version": "1.1.5",
"description": "Rogue Security AIDR real-time AI agent detection and response for Cursor",
"version": "1.1.6",
"description": "Rogue Security AIDR \u2014 real-time AI agent detection and response for Cursor",
"author": {
"name": "Rogue Security",
"url": "https://www.rogue.security"
Expand Down
4 changes: 2 additions & 2 deletions .github/plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"plugins": [
{
"name": "rogue",
"version": "1.2.4",
"description": "Rogue Security AIDR real-time AI agent detection and response for GitHub Copilot CLI",
"version": "1.2.5",
"description": "Rogue Security AIDR \u2014 real-time AI agent detection and response for GitHub Copilot CLI",
"author": {
"name": "Rogue Security",
"url": "https://www.rogue.security"
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ jobs:
SH=dash sh tests/test_hook_logs.sh
SH=bash bash tests/test_hook_logs.sh

- name: Installation pause contract
env:
ROGUE_TEST_PWSH: pwsh
run: node --test tests/test_protection.mjs

- name: Gemini dispatcher contract (node)
# The only suite that exercises hook.mjs end to end against a real server,
# and until now the only dispatcher suite CI never ran — the file existed
Expand Down
17 changes: 17 additions & 0 deletions docs/protection-pause.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Pause coding protection from Fleet

Organization owners and admins can pause a supported coding installation from its Fleet row menu. AIDR pause stops Rogue hook payload collection, evaluation, blocking, local activity logs and log uploads. The host receives its normal allow response. Management requests continue so the client can receive a resume decision.

Protocol 1 is included in the source versions Claude 1.0.30, Codex 1.0.4, Cursor 1.1.6, Copilot 1.2.5, Gemini 1.0.29, Antigravity 1.0.27 and Kiro 1.0.1. Release and deployment remain separate steps. Windows uses the PowerShell bridge; macOS and Linux use shell bridges, with Node for Gemini.

Choose a duration of one hour, four hours, 24 hours or until resumed. The reason is optional. Any organization admin may resume or change duration without another reason. AISPM discovery is controlled on the endpoint that runs scans. An independent API-key pause still applies after the installation is resumed.

The bridge exchanges its provisioning key for an installation credential at `/api/v1/hooks/protection/enroll`. A private random enrollment nonce makes retries return the same installation after a lost response. Initial enrollment outages stop collection until registration succeeds; a confirmed unsupported legacy server retains the previous bridge behavior. Identity is server-assigned and persists in the user's private protection directory. A hostname or email cannot claim an existing identity. Related host variants that use the same bridge configuration share that installation. Independent configurations remain separate. After provisioning-key rotation, the bridge restores its existing authenticated installation and reuses its saved decision and active-work tracking. An unavailable server postpones restoration without collecting activity under an unscoped key.

The bridge fetches the current decision before collecting hook input and keeps a short-lived background poller while the installation is in use. Polling runs every 15 seconds; the delivery target is 60 seconds online. The server displays applied only after a current acknowledgement. Pending, offline, unsupported or failed are not proof that local work stopped. A state-storage failure blocks the current hook and reports a failure acknowledgement. A saved finite pause expires offline; an indefinite pause remains until confirmed resume. Gemini validates both downloaded and saved decisions. An invalid response leaves the last valid decision in place; without a valid saved decision, the bridge does not collect or upload activity.

Registered installations store their hook logs inside their private protection directory and ship them with their own credential. The scoped log path takes precedence over a shared custom log destination. On resume, the shipper saves its discard offset and protection revision together, so a failed write cannot enable replay. Checkpoint failures stop that shipping run and send a failure acknowledgement. It advances past buffered content without modifying the source log. It sends only fresh subsequent entries. An endpoint's mixed-log diagnostic uploader cannot attribute these installations and must not bypass their pauses.

Older plugins and native HTTP hooks without a local gate have limited coverage. Their API-key pause can suppress server work but cannot stop local collection or CPU use. Use an updated command bridge for local suspension. Existing activity already committed before a pause remains historical.

Run `node --test tests/test_protection.mjs` for isolated HTTP and child-process checks. Set `ROGUE_TEST_PWSH=pwsh` to include the PowerShell gate. The test owns its credentials, files and polling processes; it never sends test activity to production. Signed native endpoint, Windows host and vendor application verification require their respective environments.
2 changes: 1 addition & 1 deletion plugins/antigravity/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.26
1.0.27
2 changes: 2 additions & 0 deletions plugins/antigravity/scripts/heartbeat.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ function Invoke-Main {
Initialize-Beacon # after the env files are parsed so they can set the interval
Assert-ApiKey # exits 0 when this install is not configured
Resolve-BaseUrl
. ([scriptblock]::Create((Get-Content -Raw -LiteralPath (Join-Path $pluginRoot 'scripts/protection.ps1')))) -ScriptDirectory (Join-Path $pluginRoot 'scripts')
$script:apiKey = Initialize-RogueProtection -Key $apiKey -BaseUrl $baseUrl -Slug 'antigravity' -Family 'antigravity'
Resolve-Actor
Resolve-Version
Resolve-Surface
Expand Down
2 changes: 2 additions & 0 deletions plugins/antigravity/scripts/heartbeat.sh
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ main() {
locate_plugin_root
load_env # sources the env files, then normalises the base URL
require_api_key # exits 0 when this install is not configured
. "${PLUGIN_ROOT}/scripts/protection.sh"
rogue_protection_init antigravity antigravity "${PLUGIN_ROOT}/scripts"
load_actor
resolve_version
resolve_surface "${1:-}"
Expand Down
18 changes: 15 additions & 3 deletions plugins/antigravity/scripts/hook.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ function Rotate-Log {

function Log {
param([string]$Msg)
if ((Get-Command Test-RogueProtectionCurrent -ErrorAction SilentlyContinue) -and -not (Test-RogueProtectionCurrent)) { return }
try {
if (-not $logFile) { return }
$dir = Split-Path $logFile
Expand Down Expand Up @@ -310,7 +311,8 @@ function Resolve-Actor {

# ── payload from stdin (recover UTF-8, strip BOM) ──────────────────────────
function Read-Payload {
$script:payload = [Console]::In.ReadToEnd()
$script:payload = Read-RogueProtectionInput
if (-not (Test-RogueProtectionCurrent)) { Write-Raw (Get-FailOpenDefault); exit 0 }
if (-not $script:payload) { $script:payload = '{}' }
try {
$raw = [Console]::InputEncoding.GetBytes($script:payload)
Expand Down Expand Up @@ -912,7 +914,9 @@ function Invoke-Post {
$bodyBytes = [System.Text.Encoding]::UTF8.GetBytes($payload)
$resp = ''
try {
$r = Invoke-WebRequest -Uri $url -Method Post `
if ((Get-Command Test-RogueProtectionCurrent -ErrorAction SilentlyContinue) -and -not (Test-RogueProtectionCurrent)) { Write-Raw (Get-FailOpenDefault); exit 0 }
if ((Get-Command Test-RogueProtectionCurrent -ErrorAction SilentlyContinue) -and $null -ne $script:RPRevision) { $headers['x-rogue-activity-revision']=[string]$script:RPRevision }
$r = Invoke-WebRequest -Uri $url -Method Post `
-Headers $headers -ContentType 'application/json' -Body $bodyBytes `
-UseBasicParsing -TimeoutSec 15 -ErrorAction Stop
if ($r.StatusCode -eq 200) {
Expand All @@ -922,7 +926,8 @@ function Invoke-Post {
} catch { Dbg "POST failed: $($_.Exception.Message)"; $resp = '' }

$respHead = if ($resp.Length -gt 400) { $resp.Substring(0, 400) } else { $resp }
Log "raw=$(Sanitize $respHead)"
if (-not (Test-RogueProtectionCurrent)) { Write-Raw (Get-FailOpenDefault); exit 0 }
Log "raw=$(Sanitize $respHead)"

# Fail-open on transport error, any non-200, or an empty body: emit the
# per-event default rather than relaying garbage as a decision.
Expand Down Expand Up @@ -958,6 +963,12 @@ function Invoke-Main {
Assert-ApiKey # exits before stdin is read when there is no key
Resolve-Url
Resolve-Actor
if (-not (Test-Path -LiteralPath (Join-Path $pluginRoot 'scripts/protection.ps1') -PathType Leaf)) { Write-Raw (Get-FailOpenDefault); exit 0 }
. ([scriptblock]::Create((Get-Content -Raw -LiteralPath (Join-Path $pluginRoot 'scripts/protection.ps1')))) -ScriptDirectory (Join-Path $pluginRoot 'scripts')
$script:apiKey = Initialize-RogueProtection -Key $script:apiKey -BaseUrl $script:creds['ROGUE_BASE_URL'] -Slug 'antigravity' -Family 'antigravity'
if (-not (Enter-RogueProtection)) { Write-Raw (Get-FailOpenDefault); exit 0 }
try {

Read-Payload
# Immediately after the payload, and BEFORE anything that logs or sends - the
# same position hook.sh resolves it in. Every log line from here on carries the
Expand All @@ -984,6 +995,7 @@ function Invoke-Main {
# This script MUST always exit 0: a block is carried in the relayed JSON body
# on stdout, never in the exit code.
exit 0
} finally { Leave-RogueProtection }
}

# Test seam: dot-sourcing with ROGUE_PS_LIB_ONLY=1 loads the functions above
Expand Down
14 changes: 13 additions & 1 deletion plugins/antigravity/scripts/hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ load_env() {
# periodic job on purpose: an UNCONFIGURED install writes a line per event and
# never runs anything else, so a cap enforced anywhere else would not hold.
rotate_log() {
if command -v rogue_protection_current >/dev/null 2>&1 && ! rogue_protection_current; then return 0; fi
[ -f "$ROGUE_LOG_FILE" ] || return 0
# Arithmetic, not a glob: "00" must mean zero here exactly as [int64]"00"
# and Number("00") do in the PowerShell and Node dispatchers.
Expand All @@ -133,6 +134,7 @@ rotate_log() {
}

log() {
if command -v rogue_protection_current >/dev/null 2>&1 && ! rogue_protection_current; then return 0; fi
# 0700 dir / 0600 file. The logged text is not only ours: it carries the
# server's block reason, which quotes the content that tripped the rule - a
# secret, a command, a slice of a prompt. Under the default umask the log
Expand Down Expand Up @@ -656,7 +658,8 @@ load_install_id() {
# Buffer stdin so we can enrich it (PreInvocation/PostInvocation/Stop) before
# POSTing.
read_body() {
BODY="$(cat)"
BODY="$(rogue_protection_read_input)" || { fail_open_default; exit 0; }
rogue_protection_current || { fail_open_default; exit 0; }
}

# Heartbeat, fired detached so the hook itself returns immediately regardless of
Expand Down Expand Up @@ -783,8 +786,10 @@ post_and_relay() {
fi
fi

rogue_protection_current || { fail_open_default; exit 0; }
_raw=$(printf '%s' "$BODY" | curl -sS -X POST "$URL" \
-H "x-rogue-api-key: $ROGUE_API_KEY" \
-H "x-rogue-activity-revision: ${ROGUE_PROTECTION_REVISION:-}" \
-H "x-rogue-event: $EVENT" \
-H "x-rogue-actor-email: $ROGUE_ACTOR_EMAIL" \
-H "x-rogue-actor-name: $ROGUE_ACTOR_NAME" \
Expand All @@ -798,6 +803,7 @@ post_and_relay() {
_code=$(printf '%s' "$_raw" | tail -n1)
_resp=$(printf '%s' "$_raw" | sed '$d')

rogue_protection_current || { fail_open_default; exit 0; }
log "http=$_code rc=$_rc raw=$(sanitize "$_resp" | head -c 400)"

# Fail-open on transport error, any non-200, or an empty body: emit the
Expand All @@ -823,6 +829,12 @@ main() {
stand_down_under_git_bash
locate_plugin_root
load_env # sources the env files, then every default derived from them
[ -r "${PLUGIN_ROOT}/scripts/protection.sh" ] || { fail_open_default; exit 0; }
. "${PLUGIN_ROOT}/scripts/protection.sh"
rogue_protection_init antigravity antigravity "${PLUGIN_ROOT}/scripts" "${SURFACE:-default}"
rogue_protection_enter || { fail_open_default; exit 0; }
trap 'rogue_protection_leave' EXIT

require_api_key # exits before stdin is read when there is no key
load_actor
read_body
Expand Down
Loading
Loading