heygrc
Engineeringthe heygrc team

The control-breaking five

The five pull-request patterns we built heygrc to catch first. Each one looks like a reasonable change, and each removes a control a framework depends on.

When you treat compliance as something that lives in code, a question follows: which changes actually break a control? Not in theory, in the diff. We do not have production telemetry to answer that yet, heygrc is pre-launch, so this is not a chart of what we have seen in the wild. It is the taxonomy we built heygrc to catch: five pull-request patterns that, by construction, break a control most directly.

What unites them is uncomfortable. None looks like a compliance change. Each is a reasonable edit, a cleanup, an unblock, a convenience, that happens to remove the thing a framework was relying on. Here they are.

1. A secret in the config

The fastest way to make an integration work is to put the key where the code can read it directly. It works on the first try, and it writes a live credential into the repository, its history, and every clone and CI cache that ever pulls it. A committed secret is an exposed secret, and the only safe response is to rotate it.

config/payments.ts+1 -1
- const key = process.env.PAYMENTS_SECRET_KEY+ const key = "<the live key, pasted inline>"const client = new Payments(key)
heygrcISO 27001 A.8.24

The credential now lives in version control for anyone with repo access, now or later. Read it from a managed secret store, and rotate the one that was committed.

2. Loosened auth

An authorization check is in the way of a change, so it is removed, or widened to a wildcard, to unblock a caller. The feature works for everyone after that, including the people the check existed to exclude.

api/reports.ts+0 -1
export async function getReport(user, id) {-  if (!user.can("reports:read")) return forbidden()  return reports.find(id)}
heygrcSOC 2 CC6.1

Removing the check lets any authenticated caller read any report, not just the ones they are entitled to. Restore the authorization check rather than routing around it.

3. Disabled logging

A log line is noisy, so it goes in a cleanup. The system still works; it just stops recording the security event an auditor will later sample, and that you would want after an incident.

auth/session.ts+0 -1
async function onLogin(userId, ip) {-  await audit.log("auth.login", { userId, ip })  return startSession(userId)}
heygrcISO 27001 A.8.15

The login event is no longer recorded, so it cannot be monitored or reconstructed later. Keep the log; if it is too noisy, change its level or destination rather than deleting it.

4. Widened network rules

A service cannot reach a database, so a security-group rule is opened up to make the connection work. It does work, and so does every other connection now in range, including ones from the open internet.

infra/security_groups.tf+1 -1
ingress {  from_port = 5432-  cidr_blocks = ["10.0.0.0/16"]+  cidr_blocks = ["0.0.0.0/0"]}
heygrcNIST 800-53 SC-7

Opening the rule to 0.0.0.0/0 exposes the database port to the whole internet, not just the service that needed it. Scope the rule to the source that requires access.

5. Dropped encryption

Encryption is failing in staging, so the quickest fix is to stop verifying it, and the change ships. Data that should travel authenticated and encrypted now travels in a way that can be read or tampered with in transit.

lib/http.ts+1 -0
const agent = new https.Agent({+  rejectUnauthorized: false,})
heygrcSOC 2 CC6.7

Turning off certificate verification means the connection is no longer authenticated, so it can be intercepted by a man in the middle. Keep verification on and fix the staging certificate instead.

Why these five

The pattern across all five is the same: a change that improves one thing, a working integration, an unblocked caller, cleaner output, a reachable database, a green staging run, quietly removes a safeguard a framework depends on. The compliance damage is a side effect of a reasonable goal, which is exactly why neither the author nor an ordinary review catches it. Nobody set out to weaken a control.

That is the case for reviewing the diff against the controls, at the moment the change is made. We started with these five because they are the most direct: each maps cleanly to a clause, and each is visible in the change itself. We will learn the real distribution once heygrc is running on real pull requests. Until then this is the map, not the territory, and we would rather say so.

code-reviewcontrolsshift-leftpatterns