Every SOC 2 project I have seen follows the same shape. Someone buys a compliance platform, someone drafts eighteen policies, and then six months later an auditor asks to see the code path where tenant A is prevented from reading tenant B's invoices. Everyone looks at the one engineer who wrote that middleware two years ago.
The policies say the right things. The control matrix maps to the right criteria. But the code was written by people who never read either document, because nobody expected them to.
I wanted to close that gap at the point where it opens: the moment a developer types a new route handler. If Claude is already writing a lot of that code, Claude should know what CC6.1 requires before it writes app.post('/invoices', ...).
So I went looking for a Claude skill that did this. I found two good ones, and neither was built for it.
What was already out there
kurianoff/claude-skills-soc2-policies is a set of three skills for managing policy documents. It ships 17 ready-to-use SOC 2 policy templates, a dashboard for tracking review status, a statement-by-statement review carousel with AI-assisted rewrites, and a Word export with title page, version history, and an audit-trail appendix. If your problem is "we need policies and we need to prove someone reviewed them," it is genuinely well built.
Two things kept it from solving my problem. It runs only in claude.ai Projects, because it depends on interactive widgets, per-project storage, and file download APIs that Claude Code does not have. And it is entirely about documents. The templates do not reference Trust Service Criteria, and nothing in it looks at a codebase.
alirezarezvani/claude-skills has a soc2-compliance skill in its regulatory-affairs collection that is the other half. It has a full Trust Service Criteria reference from CC1 through CC9 plus the four optional categories, a control matrix builder, a gap analyzer for Type I and Type II, an evidence tracker with readiness scoring, and solid guides on evidence collection, vendor tiers, and continuous compliance. It runs anywhere, since it is markdown and standalone Python.
What it does not do is touch code. The scripts generate and score control matrices from JSON you hand them. The word "policy" appears a handful of times, as an evidence type. There is nothing that says "when you write a route, do this."
Put side by side, the two barely overlap. One is policies, one is controls and evidence. Neither is engineering.
What I built instead
soc2-dev is a single folder you copy into any repo's .claude/skills/. It is MIT licensed and reuses content from both projects above with credit: the 17 policy templates converted to markdown, and the Trust Service Criteria reference.
The piece that makes it different is a requirement registry. Each Trust Service Criterion is translated into concrete engineering rules with IDs, a severity, and the criteria it serves. There are 66 of them across eight domains:
| ID | Requirement | TSC |
|---|---|---|
| AUTH-01 | Every non-public endpoint requires authentication. Deny by default. | CC6.1 |
| AUTH-02 | Authorization is enforced server-side per object, never from client-supplied roles or IDs. | CC6.1, CC6.3 |
| API-03 | Database, shell, and template calls use parameterization. No string-built queries. | CC6.1, CC7.1 |
| DATA-08 | Multi-tenant data access is scoped by tenant on every query. | CC6.1, C1.2 |
| LOG-03 | Logs never contain passwords, tokens, or unmasked PII. A redaction layer runs before shipping. | C1.2, CC6.1 |
| SEC-07 | Approved cryptography only. Keys live in a KMS, never alongside the data they protect. | CC6.6, C1.2 |
| CHG-01 | Protected default branch: PR required, non-author approval, no force pushes. | CC8.1 |
Everything else in the skill keys off these IDs. The domain references explain how to implement each one with code in Node, Python, and Go. The stack pattern files give runnable middleware, guards, validators, audit loggers, and encryption helpers for Express, FastAPI, Go, and Spring. The scanner reports findings by ID. The PR template asks about them by ID.
Write mode
This is the default and the reason the skill exists. When you ask Claude for a new endpoint in a repo that has the skill installed, it classifies the change, loads only the domain checklists that apply, implements the requirements alongside the feature, and annotates each enforcement point so the control is greppable:
router.post("/invoices",
requireAuth, // SOC2:AUTH-01 deny by default
validate(CreateInvoiceSchema), // SOC2:API-01 strict schema, unknown fields rejected
async (req, res) => {
// SOC2:AUTH-02 + DATA-08 tenant comes from the verified token, never the body
const invoice = await invoices.create({ ...req.body, tenantId: req.auth.tenantId });
audit.log({ event_type: "invoice.created", actor_id: req.auth.userId,
target_id: invoice.id, tenant_id: req.auth.tenantId,
outcome: "success", correlation_id: req.id }); // SOC2:LOG-01
res.status(201).json(invoice);
});
It then updates .soc2/CONTROL_MAP.md, which maps each requirement to the file and symbol that implements it, the evidence an auditor can inspect, and an owner. And it ends with a summary you paste into the PR:
SOC 2 controls in this change
- Implemented: AUTH-01, AUTH-02, API-01, API-05, LOG-01, DATA-08
- Reused existing: requireAuth (AUTH-01), auditLog (LOG-01/02)
- Skipped: API-08 (endpoint is not retried by clients)
- Exceptions applied: none
- CONTROL_MAP.md rows updated: 6
The rules Claude follows are simple and they are written down in the skill. Deny by default. Reuse an existing primitive before writing one. Never add a control the change does not touch. Never annotate a line that does not actually enforce anything. If a requirement cannot be met, record an exception with a compensating control and an expiry, never silently skip it.
Audit mode
A dependency-free Python scanner finds the violations that are findable by pattern: secrets in source, unauthenticated routes, string-built SQL, PII in logs, wildcard CORS with credentials, MD5 for passwords, TLS verification switched off, open security groups, public databases, root containers, and missing CI gates. Every finding carries a requirement ID, a severity, a confidence, and a fix hint.
| Sev | Conf | Location | Finding |
|------|--------|---------------------|-------------------------------------------------|
| high | medium | src/app.js:7 | Route has no visible authentication: GET /users/:id |
| high | medium | src/app.js:8 | SQL string concatenated with a variable |
| high | medium | src/app.js:10 | Log statement references req.body.password |
| critical | high | .env:1 | Connection string with password found in source |
It runs as a CI gate with --fail-on high, and as a pre-commit hook with --staged, which scans only the change set while still reading the whole repo for stack and middleware detection so a staged route is not falsely reported as unauthenticated because its middleware lives in another file.
The scanner is honest about what it cannot see. Object-level authorization, tenant scoping, field-level encryption, CSRF, and session timeouts are invisible to any regex. The skill's audit mode names those explicitly as manual-review items, and they are where most real audit exceptions come from.
Evidence and policy modes
Evidence mode regenerates the control map, a routes manifest listing every endpoint with its auth requirement and data classification, and an evidence index that says where each control's proof lives: a CI job name, a branch protection export, a log query, an IAM policy dump.
Policy mode drafts organizational policies from the 17 templates and appends a "Technical enforcement" section to each one listing the requirement IDs that implement it. The Access Control Policy points at AUTH-01 through AUTH-10. The Cryptography Policy points at SEC-07 and DATA-02. That is the link the two earlier projects never made: policy text on one side, enforcement points on the other, with IDs in between.
What the earlier repos had that I kept, and what was missing
| kurianoff | alirezarezvani | soc2-dev | |
|---|---|---|---|
| Policy templates | 17, HTML, with review workflow | none | the same 17, markdown, mapped to requirement IDs |
| TSC reference | none | full CC1-CC9, A1, C1, PI1, P1-P8 | reused with credit |
| Control matrix and gap analysis | none | scripts over JSON you supply | derived from the code itself via scanner and control map |
| Code-level requirements | none | none | 66, with per-stack implementations |
| Enforced while writing code | no | no | yes, that is the default mode |
| Traceability from code to criterion | none | none |
SOC2:<ID> annotations plus control map |
| Runs in Claude Code | no | yes | yes |
| Tests and CI for the tool itself | no | partial | 24 regression tests, three Python versions, self-scan, gitleaks |
I want to be fair to both projects. The kurianoff review carousel is a better policy-review experience than anything I built, and if you are in claude.ai Projects it is worth using for that. The alirezarezvani TSC reference is thorough enough that I did not rewrite it. What neither one had was the idea that compliance is something a developer does at the keyboard, not something a compliance team does to the codebase afterward.
What I learned shipping it
Regex scanners lie in both directions. The first pass flagged "DELETE " + url as SQL injection and next_token as a leaked secret. A teammate's improvements to the scanner added coverage and also added three high-severity false positives that would have blocked ordinary commits. The fix was a regression suite with a false-positive fixture that must produce nothing at medium or above, alongside the true-positive fixture where every planted violation must fire with the right ID. Scanner changes now require both.
A gate that can silently pass is worse than no gate. Staged mode resolved git paths against the wrong directory, so running it from a subdirectory reported zero findings and exited clean. On macOS, temp directories go through a symlink and the same thing happened. Tests caught it. If you ship a compliance tool without tests, the first auditor who runs it against your own repo will find that out for you.
Your own tool should pass its own scan. The repo runs the scanner on itself in CI. The first run flagged that the repo had no CI, no PR template, and no CODEOWNERS. That was uncomfortable and correct.
GitHub push protection will reject your test fixtures. A fake Stripe key in a test file blocked the push. The fixture is now assembled at runtime so the committed source never contains a string that matches a live-key pattern. Gitleaks then flagged a fake Dockerfile token in the same file. An allowlist scoped to the fixture directory fixed it without exempting anything else.
Try it
git clone https://github.com/aggtushar123/soc2-skills.git /tmp/soc2-skills
mkdir -p .claude/skills
cp -r /tmp/soc2-skills/skills/soc2-dev .claude/skills/soc2-dev
python3 .claude/skills/soc2-dev/scripts/soc2_init.py . --company "Your Co"
python3 .claude/skills/soc2-dev/scripts/soc2_scan.py . --format md
Then, in Claude Code, ask for something like "add a POST /invoices endpoint for the current tenant" or "audit this repo for SOC 2" and watch what it loads.
It is v0.1.0 and I am calling it a public beta. The Claude-facing half is stable. The scanner's patterns will keep being tuned, and the test suite is there so tuning does not regress. Issues and pull requests are welcome, especially stack patterns for frameworks I have not covered.
SOC 2 is still an attestation over organizational controls and evidence gathered over an observation period. No skill makes you compliant. But the engineering half can be done right from the first commit instead of reconstructed at audit time, and that is the half this is for.
Repo: github.com/aggtushar123/soc2-skills. Credits to Constantine Kurianoff for the policy templates and Alireza Rezvani for the Trust Service Criteria reference, both MIT.
Top comments (0)