EXAMPLE A
S3 Bucket Public Exposure Check
Fires when a new S3 bucket is detected or a policy change event arrives. Calls the AWS-compatible GetBucketAcl endpoint, extracts the permission grants, runs an LLM risk assessment, then branches on severity — high risk triggers a block-public-access remediation output; low risk logs and monitors.
name: S3 Bucket Public Exposure Check
version: "1.0"
trigger: alert_triggered
description: >
Checks S3 bucket ACL for public grantees, assesses risk with
GPT-4o-mini, and routes to block-public-access or monitor.
inputs:
- name: bucket_name
description: "S3 bucket name (e.g. my-company-assets)"
- name: aws_region
description: "AWS region (e.g. us-east-1)"
- name: account_id
description: "AWS account ID for context"
- name: event_source
description: "Who/what triggered this check (CloudTrail, scheduler)"
steps:
# 1. Parse and normalise inputs
- id: parse_event
type: transform
with:
compute:
bucket: "'{{ inputs.bucket_name }}'"
region: "'{{ inputs.aws_region }}' || 'us-east-1'"
account: "'{{ inputs.account_id }}'"
# 2. Fetch bucket ACL from AWS-compatible endpoint
# Replace with your real AWS API gateway or signed-request proxy.
- id: fetch_bucket_acl
type: http.get
on_failure: continue
with:
url: "https://s3.{{ inputs.aws_region }}.amazonaws.com/{{ inputs.bucket_name }}?acl"
headers:
Accept: "application/json"
# 3. Extract public grantees from the ACL response
# AllUsers / AuthenticatedUsers URIs indicate public exposure.
- id: extract_grantees
type: transform
with:
compute:
acl_raw: "JSON.stringify('{{ steps.fetch_bucket_acl.output.body }}')"
is_public: "JSON.stringify('{{ steps.fetch_bucket_acl.output.body }}').includes('AllUsers') ? 'true' : 'false'"
is_auth_users: "JSON.stringify('{{ steps.fetch_bucket_acl.output.body }}').includes('AuthenticatedUsers') ? 'true' : 'false'"
acl_summary: "JSON.stringify('{{ steps.fetch_bucket_acl.output.body }}').includes('AllUsers') ? 'PUBLIC: AllUsers read access detected' : 'No AllUsers grantee found'"
# 4. LLM risk assessment — MITRE ATT&CK + remediation advice
- id: assess_risk
type: llm.analyze
with:
model: gpt-4o-mini
temperature: 0.1
json_output: true
system: "You are a senior cloud security engineer specialising in AWS S3 misconfigurations."
prompt: |
Bucket name: {{ inputs.bucket_name }}
Region: {{ inputs.aws_region }}
Account: {{ inputs.account_id }}
ACL summary: {{ steps.extract_grantees.output.acl_summary }}
Is public (AllUsers): {{ steps.extract_grantees.output.is_public }}
Is auth-users exposed: {{ steps.extract_grantees.output.is_auth_users }}
Assess the exposure risk. Return JSON with these fields:
{ "risk": "critical|high|medium|low", "confidence": 0.0-1.0,
"rationale": "...", "mitre_technique": "T####.###",
"recommended_action": "...", "urgency": "immediate|24h|72h|monitor" }
# 5. Compute numeric risk score
- id: score
type: transform
with:
compute:
public_bonus: "'{{ steps.extract_grantees.output.is_public }}' === 'true' ? 50 : 0"
auth_bonus: "'{{ steps.extract_grantees.output.is_auth_users }}' === 'true' ? 30 : 0"
confidence_mul: "Number('{{ steps.assess_risk.output.parsed.confidence }}') || 0.5"
base_risk: "(('{{ steps.extract_grantees.output.is_public }}' === 'true') ? 50 : 0) + (('{{ steps.extract_grantees.output.is_auth_users }}' === 'true') ? 30 : 0)"
risk_score: "Math.min(Math.round(((('{{ steps.extract_grantees.output.is_public }}' === 'true') ? 50 : 0) + (('{{ steps.extract_grantees.output.is_auth_users }}' === 'true') ? 30 : 0)) * (Number('{{ steps.assess_risk.output.parsed.confidence }}') || 0.8) + 10), 100)"
# 6. Branch on risk score — remediate if >= 60
- id: route
type: conditional
with:
if: "steps.score.output.risk_score >= 60"
# 7a. High-risk path: emit block-public-access remediation
- id: remediate
type: output
if: "steps.route.output.result == true"
with:
value:
verdict: "{{ steps.assess_risk.output.parsed.risk }}"
score: "{{ steps.score.output.risk_score }}"
recommended_action: block_public_access
mitre_technique: "{{ steps.assess_risk.output.parsed.mitre_technique }}"
rationale: "{{ steps.assess_risk.output.parsed.rationale }}"
urgency: "{{ steps.assess_risk.output.parsed.urgency }}"
is_public: "{{ steps.extract_grantees.output.is_public }}"
bucket: "{{ inputs.bucket_name }}"
# 7b. Low-risk path: log and monitor
- id: monitor
type: output
if: "steps.route.output.result == false"
with:
value:
verdict: "{{ steps.assess_risk.output.parsed.risk }}"
score: "{{ steps.score.output.risk_score }}"
recommended_action: monitor
rationale: "{{ steps.assess_risk.output.parsed.rationale }}"
bucket: "{{ inputs.bucket_name }}"