Skip to content

fix(recipes): resolve security-group direction from the Terraform state layer - #29

Open
safwentrabelsi wants to merge 2 commits into
mainfrom
fix/sg-recipe-direction
Open

safwentrabelsi wants to merge 2 commits into
mainfrom
fix/sg-recipe-direction

Conversation

@safwentrabelsi

Copy link
Copy Markdown
Contributor

The recipe was wrong

Shipped an hour ago in #27, it said:

Direction is not modelled — do not report these as "open to the internet". The graph stores no ingress/egress flag on a rule (no is_egress, no distinct edge)…

That is true of the cloud-scanned EC2_SECURITYGROUPRULE, and false of the graph. Terraform state models the two as separate resource types, so the graph carries them as distinct labels joined to the cloud rule by MANAGES:

  • AWS_VPC_SECURITY_GROUP_INGRESS_RULE — 165 nodes
  • AWS_VPC_SECURITY_GROUP_EGRESS_RULE — 65 nodes

Found by Claude using the plugin on anyshift_infrastructure: it went past the caveat, used the state layer, and reported 14 genuine internet-facing ingress rules. Verified, and 14 is exactly right.

wide-open rules count
egress 25
ingress 14
unknown (no state node) 24

What changed

The query returns direction, and the caveat now says only ingress is exposure. unknown means the rule has no Terraform-state node — console-created or managed elsewhere — not that it is inbound; the ruleDescription and proto = '-1' heuristics stay, demoted to triage for that case alone.

Re-verified verbatim from the file: 50 rows in 1.6 s, 14 ingress / 25 egress / 11 unknown within the LIMIT.

npm test 40/40, validator passes. No version bump here — say the word and I cut v0.3.3, since v0.3.2 shipped the wrong caveat to Codex users.

🤖 Generated with Claude Code

safwentrabelsi and others added 2 commits September 9, 2026 18:30
…te layer

The recipe told the reader direction was not modelled and to confirm in AWS.
That is wrong. Terraform state models ingress and egress as separate resource
types, so the graph carries them as distinct labels — AWS_VPC_SECURITY_GROUP_
INGRESS_RULE and ..._EGRESS_RULE — joined to the cloud-scanned rule by MANAGES.

Found by Claude using the plugin on anyshift_infrastructure: it separated the
two and reported 14 genuine internet-facing ingress rules. Verified: the 63
wide-open rules are 14 ingress, 25 egress and 24 unknown, and 14 is exactly
right.

The recipe now returns `direction` and says only ingress is exposure. 'unknown'
means the rule has no state node (console-created, or managed outside
Terraform), not that it is inbound — the two description/protocol heuristics
stay, demoted to triage for that case only.

Re-verified verbatim from the file: 50 rows, 1.6 s, 14 ingress / 25 egress /
11 unknown within the LIMIT.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The direction column resolves from Terraform state. A project without the
integration has no state nodes, so every row reads 'unknown' — that is the
column being unavailable, not every rule being suspicious. Says to check
describe_schema for the label before relying on it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 667734d965

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

count(DISTINCT t) AS attachedCount,
collect(DISTINCT coalesce(t.name, t.hashedID))[0..5] AS attachedTo
ORDER BY attachedCount DESC, securityGroup LIMIT 50
ORDER BY direction, attachedCount DESC, securityGroup LIMIT 50

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Put ingress rows ahead of the row limit

In any project with at least 50 wide-open egress rules, ascending lexical ordering places every egress row before every ingress row, so LIMIT 50 can omit all actual exposures. Since the guidance below treats only ingress as actionable, this security recipe can produce a false-negative result; use an explicit priority that puts ingress first, filter by direction, or paginate each direction separately.

Useful? React with 👍 / 👎.

// Direction comes from the Terraform-state layer, which models ingress and egress
// as separate resource types. Rules AWS created outside Terraform have no state
// node and stay 'unknown'.
OPTIONAL MATCH (st)-[:MANAGES]->(r)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require the Terraform-state node to be live

When a Terraform resource has been removed while its AWS rule survives, the deleted state node retains its MANAGES relationship, as documented by the current-state invariant in skills/agent-plugin/SKILL.md. Because this match omits :ALIVE, the recipe treats that historical node as current Terraform evidence and bypasses the required unknown-direction verification; constrain st to live nodes.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant