InfraSec: the change that passes CI and still hurts you
Coding and Agentic Engineering track ยท Nebius x NVIDIA Global AI Hackathon
The silent killer
The most dangerous infrastructure change is the one that passes CI.
It formats. It validates. The tests are green. And buried in the diff is an SSH rule that went from a private range to 0.0.0.0/0, an IAM policy that quietly got wider, a bucket that is now public, or a resource type this repo has never run before. CI answers "is this valid Terraform?" It does not answer "is this safe to run in production?" Nobody finds out until something breaks, or someone else finds it first.
I learned this deploying ML models. The model was never the hard part. The bottleneck was the infrastructure around it: networking, permissions, buckets, GPU cost, and the unknown risk hiding in every change. In the age of AI, more code ships faster, and more of it is infrastructure that nobody fully reviews. That is why I built InfraSec: a tool that makes sure the infrastructure change is secure before it ships.
What InfraSec does
InfraSec reviews Terraform changes before they reach production. The principle behind it is one sentence: AI does the work. Deterministic code decides. A human approves what reaches production.
- Gate (deterministic). A plain-Python evaluator reads the
terraform show -jsonplan and returnsauto_applyorhold. No model is involved, no AWS account is needed, and the same plan always gives the same answer. It also records whether conventional CI passed, so the report can say whether CI alone would have shipped the change. - Explain (NVIDIA Nemotron). When the gate holds a change, Nemotron writes a short note on the merge request: what changed, why it was held, and a rollback plan.
- Fix (NVIDIA Nemotron, verified by the gate). If every finding is a pure tightening (for example
NETWORK_OPEN_TO_WORLD), Nemotron proposes the corrected.tffile. The gate then re-checks the patched plan, and the fix is kept only if the gate now saysauto_apply. Otherwise it is reverted and a human is asked. - Human approval. Staging deploys after the gate passes.
deploy:productionis a manual job that waits for a person.
The model never invents the risk number. It explains and proposes. The verdict always comes from code that can be audited.
The demo in one scenario
The demo target is demo-target/main.tf, a security group with SSH open to 10.0.0.0/8. Change the CIDR to 0.0.0.0/0, push, and open a merge request. terraform fmt and validate pass, so conventional CI is green. InfraSec holds the change anyway. Nemotron explains the finding on the merge request and proposes the fix. The gate re-checks it, the fix is committed back to the branch, the pipeline re-runs and passes, and the merge request merges. Production still waits for a person.
How it is built
- Decision engine (Python, standard library):
agent/holds the plan parser, OIDC trust diff, cost estimate and decision engine. The rules are fail-closed: widened OIDC trust, any IAM or policy resource touched, a new resource type, a cost change of at least $2/month or 40% of baseline, and any parse error all result inhold. - Offline gate:
scripts/infrasec_decide.pyscores a saved or fresh plan with no network. - Decision API: the same evaluator is deployed as an IAM-authenticated AWS Lambda Function URL (SigV4), with an installable
infra-riskclient for other pipelines. - Pipeline: GitLab CI jobs for plan, conventional checks, the gate, explain, fix, notify, auto-merge and staging and production deploys.
terraform/provisions the Lambda, OIDC access and decision history. - Nemotron explain:
scripts/infrasec_explain.pysends only the decision JSON and the Terraform diff (both size-capped) and asks for three fixed sections. It strips any<think>output, and it exits 0 in every case, so it can never break a pipeline. - Nemotron fix:
scripts/infrasec_fix.pyruns only when every finding is auto-fixable. Guardrails apply before anything is written: one existing.tffile, at most 10 changed lines, and an identical set of resources. The deterministic gate has the final say, and an attempt budget caps retries. - Tests: pytest with a local fake server, so no API key is needed. The fix tests include a bad proposal, an oversized diff and a changed resource set, and all of them must leave the file untouched.
The full technical detail, decision rules and setup steps are in the README.
Tooling: what is used, and where
Being precise about this matters for this hackathon.
- NVIDIA Nemotron: the open-source model that writes the explanations and proposes the fixes. Model used: nvidia/nemotron-3.5-bolt-e4b.
- Endpoint used for the demo run: NVIDIA-hosted API,
https://integrate.api.nvidia.com/v1. This is not Nebius. I could not complete Nebius biling verification because i was using an Mpesa Global Virtual Card. I have no live Token Factory run to show. - Nebius Token Factory: the default endpoint in the code (
https://api.tokenfactory.nebius.com/v1). The client speaks the OpenAI-compatible API, so switching is three environment variables:NEBIUS_API_KEY,NEBIUS_MODEL_IDandNEBIUS_BASE_URL. The merge-request notes name the real provider, derived from the base URL. - GitLab CI/CD: pipeline wiring and the manual production gate.
- AWS Lambda and Terraform: the decision API and its infrastructure.
- GitLab Duo flow: built earlier for the GitLab AI Hackathon, kept as an alternative to the Nemotron fixer.
- Fastino GLiNER2.5-Decide: an optional advisory classifier whose output never changes the deterministic action.
- Python standard library and pytest: no SDK to install for the model calls.
Friction log
What got in the way.
- Nebius account verification. I could not get a verified Token Factory account in time. That is the reason the demo ran against NVIDIA-hosted Nemotron, and it is the biggest gap in this submission.
- Environment variables named for a provider I wasn't using. The code reads
NEBIUS_API_KEY, but the value was annvapi-key from NVIDIA, with a different base URL and a different model id format. It works, but it is easy to get wrong. - A label that told the wrong story. The merge-request notes were hard-coded to say "on Nebius Token Factory", even when the calls went to NVIDIA. I caught it and fixed it: the label is now derived from
NEBIUS_BASE_URL, and the README and submission notes disclose the run honestly. - A timeout I first mistook for rate limiting. I was inside a 40 requests-per-minute allowance and still saw failures. The real error was a 120-second timeout with no response headers from the hosted endpoint, not a 429. Large models queue on shared hosted tiers, so switching to a smaller model, retrying, and keeping the model step advisory (it can never fail the pipeline) was the fix.
- Models don't always return what you ask for. Reasoning models emit
<think>blocks, and a model asked for a raw file may add prose or code fences. That is why the fix path extracts the file, then applies hard guardrails, then lets the gate decide. - Two repositories, one story. The project started in the GitLab hackathon repository. The public GitHub copy still showed the older README with the GitLab template text and no Nemotron section, so judges would have seen an out-of-date project. Syncing it was a last-hour job.
- Secrets hygiene. A
.envfile with a real key is onegit add -Aaway from being public, so.envgoes in.gitignorebefore anything else.
Accomplishments that we're proud of
- A gate that catches what CI misses. An SSH rule moving from
10.0.0.0/8to0.0.0.0/0passesfmtandvalidate, and InfraSec still holds it. The verdict is deterministic: no model, no AWS account, and the same plan always gives the same answer. - The model proposes, the gate decides. Nemotron's fix is kept only if the gate re-checks the patched plan and says
auto_apply. Otherwise it is reverted and a human is asked. - Guardrails that are tested, not assumed. A bad proposal, an oversized diff and a changed resource set all leave the file untouched, and the tests run against a local fake server with no API key.
- A model step that can never break the pipeline. The explain job exits 0 in every case, so a slow or failing endpoint costs a note, not a deploy.
- Production still waits for a person. The whole loop, from hold to explanation to verified fix to merge, runs without touching
deploy:production, which stays manual. - Honest provider labelling. I caught the merge-request notes claiming "Nebius Token Factory" on an NVIDIA-hosted run, derived the label from the base URL, and disclosed the run plainly in the README and this write-up.
What we learned
- Verification is what makes a model usable in a security path. Asking a model to judge risk is fragile. Asking it to propose a change that a deterministic check then accepts or rejects is not.
- Read the actual error. I took a 120-second timeout with no response headers for rate limiting, even though I was inside the 40 requests-per-minute allowance. It was queueing on a shared hosted tier, and the fix was a smaller model, retries and an advisory model step.
- Models don't return what you ask for. Reasoning models emit
<think>blocks, and a model asked for a raw file may add prose or code fences. Extract, apply hard guardrails, then let the gate decide. - An OpenAI-compatible API makes switching providers cheap, but naming matters. Three environment variables are enough to move between endpoints, yet
NEBIUS_API_KEYholding annvapi-key is an easy mistake to make. - Start account onboarding first. Biiling setup blocked the platform I built for, and I had to build around it. It is the biggest gap in this submission.
- Keep the story in sync. Two repositories meant an out-of-date public README, and secrets need
.gitignorebefore anything else.
Where it goes from here
- Run it live on Token Factory. The first step is finishing Nebius verification and recording a real run. Then compare models by job: a fast Nano or Super model for explanations, and Nemotron Ultra only for the fix proposals that need real reasoning.
- ML-infrastructure rules. This is where the idea started. Add signals for GPU instance types and their cost, publicly reachable inference endpoints, exposed model and dataset buckets, and training roles with broad permissions.
- More auto-fixable findings. Extend the set of pure tightenings the gate can verify, keeping the same rule: the model proposes, the gate decides.
- Run on GitHub Actions too. A workflow example already exists for the Decision API, and it needs a full explain and fix path.
- Policy as data. Let teams tune thresholds and rules per repository, with every decision logged for audit.
What was significantly updated during the Submission Period
InfraSec existed before this hackathon: the deterministic gate, the GitLab Duo explain-and-fix loop, the suggester and the AWS decision API were built for the GitLab AI Hackathon and are frozen. New in this period:
- Nemotron explanation (
scripts/infrasec_explain.py) and the Nemotron-proposed, gate-verified fix (scripts/infrasec_fix.py), with their tests. - The
infrasec:explainandinfrasec:fixpipeline jobs. - A manual
deploy:productiongate.
The evaluator itself is unchanged by design.
Feedback
Nebius Token Factory
- Onboarding: the billing set up step blocked me from using the platform at all before the deadline. Clearer status on what verification needs and how long it takes would have saved me from building around it. Another alternative could be for a hackathon like this, there should be no need for billing setup even if it's temporary.
- The OpenAI-compatible API is the right call. It let me write the client once and point it at either endpoint by changing a base URL.
- Suggestion: For a hackathon like this, there should be no need for billing setup even if it's temporary.
NVIDIA Nemotron and NVIDIA-hosted API
- Quality of explanations for Terraform diffs: The are detailed.
- Latency: large models on the hosted tier sometimes sat in a queue for over 120 seconds with no response headers. A visible queue or status indicator would help developers tell "overloaded" from "broken".
Built with
NVIDIA Nemotron, Nebius Token Factory (default endpoint), NVIDIA-hosted API (demo run), Python, Terraform, GitLab CI/CD, AWS Lambda, pytest, GitLab Duo, Fastino GLiNER2.5-Decide.
Built With
- aws-lambda
- fastino-gliner2.5-decide
- gitlab
- nebius-token-factory
- nvidia-hosted-api
- nvidia-nemotron
- terraform
Log in or sign up for Devpost to join the conversation.