← all branches

feat/sec-base

needs attentionviewing older commit
aaec085 · incrementalpre-PRreviewed 2026-07-07 17:59 UTC7H · 11M · 9L · 5I
The branch
Purpose
Land the repo-implementable security baseline for ISO 27001 + SOC 2 certification: CI scanning controls (Dependabot, CodeQL, advisory audit), CDK stack authoring for CloudTrail/GuardDuty reconciled to live manual resources, GitHub org security settings, and compliance knowledge base (program SSOT + 18 policy skeletons).
Goal
Complete all 4 SGs of the security baseline so the certification program has a verified repo foundation before auditor selection and observation window begin.
Sub-goals
  • SG-1: Dependency & code scanning — .github/dependabot.yml + codeql.yml + advisory audit job in pr-checks (commit 734c0650)
  • SG-2: CDK security baseline reconcile — AuditTrailStack + GuardDutyBaselineStack authored, drift vs live reality documented, deploy deferred pending cdk import (commit 86b26653)
  • SG-3: GitHub org security settings via gh api — secret scanning, push protection, Dependabot alerts enabled org-wide (commit 6f3d4f74)
  • SG-4: Compliance knowledge base — compliance.md SSOT + 18 policy skeletons + README; prior art from spike/iso27001-readiness folded in (commits b928e2d3, aaec085e)
The changes (whole branch)
What
27 files, +639/-170 lines cumulative vs main. New: .github/dependabot.yml, .github/workflows/codeql.yml, docs/compliance/policies/ (18 policies + README), .claude/rules/compliance.md. Modified: .github/workflows/pr-checks.yml (+security-audit job), infra/cdk/src/stacks/audit/audit-trail.stack.ts (reconcile + defer comment), infra/cdk/src/stacks/security/guardduty-baseline.stack.ts (org runbook + defer comment), .branch/scope.md (all SGs marked complete + intent log).
Why
Phase 0 of the combined ISO 27001:2022 + SOC 2 Type II certification program. All repo-side prerequisites must land before the observation window begins (~M3-M4 from program start). The stacks are authored but not deployed — deploy is a separate explicit decision (management account, cdk import path).
Areas
.github/+12242infra/cdk/src/stacks/+468.claude/rules/+630docs/compliance/policies/+3510.branch/+57120
Blast
27 files, +639/-170 lines. No runtime or domain code changed — all changes are CI configuration, CDK infrastructure authoring (deploy-deferred), documentation, and compliance policies. Zero production blast radius; CI workflows affect all PR runs going forward.
GHAS billing sign-off pending (CodeQL + secret scanning may not be active) AuditTrailStack and GuardDutyBaselineStack are AUTHORED ONLY — never deploy without cdk import OIDC deployer roles still have AdministratorAccess (pre-existing finding, not introduced here) GuardDuty member account enrollment unverified — run step 3 of org runbook before observation window
CI (pr-checks)· No PR exists for this branch — CI status not availableCodeRabbit· No .coderabbit.yaml in repocdk synth· Branch scope.md confirms cdk synth passes for AuditTrailStack and GuardDutyBaselineStack

Findings · 29

correctness2

medium

managementEvents: undefined relies on undocumented AWS/CDK default behavior

infra/cdk/src/stacks/audit/audit-trail.stack.ts:71

When managementEvents is undefined, CDK L2 emits no EventSelectors entry and AWS falls back to logging all management events. The comment says this is intentional, but it's fragile — a CDK version change could silently alter behavior and break audit coverage. Use explicit ReadWriteType.ALL to match the live batu-org-trail configuration.

low

CodeQL concurrency group merges push-to-main and scheduled runs — scheduled scans can be cancelled

.github/workflows/codeql.yml:19

Both push-to-main and schedule trigger with github.ref=refs/heads/main, sharing the concurrency group. cancel-in-progress: true can drop the weekly security scan. Append ${{ github.event_name }} to the group key.

security11

high

GitHub Actions workflows use mutable tag refs, not pinned SHAs

.github/workflows/codeql.yml:35

actions/checkout@v4, github/codeql-action/init@v3, github/codeql-action/analyze@v3 (and equivalent in pr-checks.yml) use mutable tags. A force-pushed tag silently executes attacker-controlled code with security-events:write + contents:read. ISO 27001 A.8.30 / SOC 2 CC6.8. Dependabot (github-actions ecosystem, now configured) will open update PRs — pin all uses: lines to full 40-char SHA now to close the supply-chain window.

high

CI OIDC deployer roles attach AdministratorAccess — least-privilege violation

infra/cdk/src/stacks/cicd/github-oidc-roles.stack.ts:55

All three deployer roles (dev/stg/prod) attach AWS-managed AdministratorAccess. A compromised workflow can read all Secrets Manager secrets (CFE credentials — crown jewel), modify IAM, or delete infrastructure. Pre-existing finding recorded in compliance.md (A.8.2 / CC6.3) but not fixed in this branch. Must be scoped to CDK deploy surface before Stage 2.

high

CloudTrail S3 lifecycle expires logs at 365 days — borderline for ISO 27001 Stage 2

infra/cdk/src/stacks/audit/audit-trail.stack.ts:59

365-day expiration satisfies SOC 2 minimum (1 year) but ISO 27001 Stage 2 observation ends ~M11; auditors routinely expect 2–3 years of logs. The data-retention-disposal-policy.md says '1yr' — consistent but may need raising before fieldwork. Recommend Duration.days(1095) with Glacier transition at 90 days.

high

CloudTrail S3 bucket uses SSE-S3 instead of KMS-CMK — no key management audit chain

infra/cdk/src/stacks/audit/audit-trail.stack.ts:51

SSE-S3 means AWS controls the encryption keys; KMS-CMK key usage is itself logged in CloudTrail, creating an independent tamper-evident chain for restricted data access. ISO 27001 A.8.24 (cryptography rotation), A.8.15. Also missing MFA Delete — a compromised identity could purge audit evidence. Switch to BucketEncryption.KMS_MANAGED with a CMK before cdk import.

medium

CloudTrail data events for Secrets Manager not enabled — CFE credential reads unattributable

infra/cdk/src/stacks/audit/audit-trail.stack.ts:71

access-control-policy.md and logging-monitoring-policy.md both commit to logging reads of restricted-class secrets (CFE credentials). With data events off, an insider or compromised Lambda reading customer CFE credentials leaves no traceable record. ISO 27001 A.8.15, SOC 2 CC7.2. Add Secrets Manager data events scoped to batu/*/cfe/* ARNs — cost is proportional to secret access frequency, not total volume.

medium

GuardDuty findings have no routing to an alert channel — detection without response

infra/cdk/src/stacks/security/guardduty-baseline.stack.ts:47

Detector enabled but no EventBridge rule or SNS routing for findings. logging-monitoring-policy.md requires 'GuardDuty high-severity' findings trigger alerts with an on-call owner. SOC 2 Type II auditors observe whether alerts actually fire during the observation window, not just whether the detector exists. Add a CfnFilter + EventBridge rule routing HIGH/CRITICAL findings to SNS even with a placeholder destination.

medium

GuardDuty member account coverage unverified — org-wide threat detection assumed

infra/cdk/src/stacks/security/guardduty-baseline.stack.ts:1

Member account status for dev/stg/prod was unverified on 2026-07-07 (SSO expired). Customer workloads run in prod (353625676934) and stg (966363209222). Claiming GuardDuty as a control without verified enrollment is a Type II audit risk. Run the runbook step 3 (aws guardduty list-detectors per profile), record dated evidence, and complete org auto-enable before the observation window begins.

medium

Dependabot github-actions ecosystem not grouped — major version bumps uncontrolled

.github/dependabot.yml:25

npm ecosystem has minor-and-patch grouping; github-actions has none. Major action version bumps require behavioral review (especially for codeql-action). Without update-type filtering, a wave of major updates could also saturate the 3-PR limit, blocking security-relevant minor/patch updates. Add update-types constraint to separate major bumps.

low

Hardcoded live GuardDuty detector ID in stack source comment

infra/cdk/src/stacks/security/guardduty-baseline.stack.ts:9

The live detector ID (64cb2317...) is embedded in source. Minor information disclosure; more importantly creates documentation debt if the detector is ever recreated. Move to docs/compliance/ or SSM; reference by path in the runbook.

info

GHAS billing pending sign-off — CodeQL and secret scanning may not be operational

.github/workflows/codeql.yml:1

Code security / secret protection are metered GHAS add-ons on the Team plan. Until billing is accepted, CodeQL runs fail with a licensing error. The ISO 27001 / SOC 2 evidence package cites A.8.28, CC8.1 — auditors will verify these controls are actually operational, not just configured. Obtain founder sign-off immediately; this is a Phase 0 prerequisite.

info

Policy review cadences commit to quarterly access reviews — operational capacity check needed

docs/compliance/policies/access-control-policy.md:7

Quarterly reviews of 6+ systems with dated sign-off records is a significant commitment for a 3-person team. Type II auditors will request 4 quarterly records with completion timestamps. Confirm who generates the review, where records are stored, and whether quarterly is sustainable — or reduce to semi-annual with a compensating control before sign-off.

conventions3

high

GuardDutyBaselineStack: CfnDetector missing RemovalPolicy — default is DESTROY

infra/cdk/src/stacks/security/guardduty-baseline.stack.ts:47

CfnDetector defaults to RemovalPolicy.DESTROY. Accidental stack deletion permanently destroys the sole threat-detection control per account. The audit log bucket correctly sets RemovalPolicy.RETAIN — apply the same protection: detector.applyRemovalPolicy(RemovalPolicy.RETAIN).

high

AuditTrailStack: batu:dataClass=internal under-classifies org-wide audit logs

infra/cdk/src/stacks/audit/audit-trail.stack.ts:40

The app-level baseline tags the audit S3 bucket and Trail as batu:dataClass=internal. These logs contain API call metadata for Secrets Manager paths holding CFE credentials (classified 'restricted' in compliance.md). Per infrastructure.md tagging standard, dataClass must reflect the highest classification stored. Add a Tags.of(this).add('batu:dataClass', 'restricted') override in the stack constructor.

high

GuardDutyBaselineStack: missing batu:dataClass override for security findings

infra/cdk/src/stacks/security/guardduty-baseline.stack.ts:47

GuardDuty detector handles org-wide threat detection over restricted-class data. Tagged 'internal' by the app-level baseline. Should be overridden to 'confidential' at minimum, matching how other security-layer stacks handle data classification.

tests4

low

No CDK snapshot tests for AuditTrailStack or GuardDutyBaselineStack

infra/cdk/src/stacks/audit/audit-trail.stack.ts:1

No CDK assertions tests verify that isOrganizationTrail, enableFileValidation, isMultiRegionTrail are set as intended. A refactor could silently flip security-critical properties. CDK aws-cdk-lib/assertions Template.fromStack snapshot tests would provide a CI regression guard for these audit-critical config values.

low

Dependabot covers npm + github-actions but not Terraform (infra/posthog)

.github/dependabot.yml:1

The posthog-checks CI job applies Terraform IaC. No terraform ecosystem entry for infra/posthog/ means provider version bumps and CVE-bearing provider releases are never automatically surfaced. For ISO 27001 A.8.8 completeness, add a terraform ecosystem block.

info

Policy skeleton files have no structural validation in CI

docs/compliance/policies:1

18 policy .md files have placeholder sections (owner, review date, version) with no CI check verifying required fields are populated. A lightweight script checking for required front-matter would prevent skeleton policies from being merged without ownership metadata, serving as evidence of controlled policy lifecycle.

info

CodeQL failure mode when GHAS not enabled is silent in PR checks

.github/workflows/codeql.yml:1

No status badge or observability mechanism confirms whether CodeQL scans are actually succeeding. Easy to forget the GHAS billing action item. Adding a Slack/GitHub notification on CodeQL failure (even when non-blocking) would make the control's active state visible.

improvement9

medium

CodeQL concurrency group cancels scheduled weekly scans on push-to-main

.github/workflows/codeql.yml:19

Both push-to-main and scheduled runs share the same concurrency group (same github.ref = refs/heads/main). cancel-in-progress: true drops scheduled scans when a merge lands during their window. Fix: '${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}' with cancel-in-progress: ${{ github.event_name == 'pull_request' }}'.

medium

security-audit job sets up pnpm+Node unnecessarily — no install needed

.github/workflows/pr-checks.yml:139

The step comment says 'Audits pnpm-lock.yaml directly — no install needed,' yet the job still sets up pnpm and Node.js (steps at lines 143–151). pnpm audit only reads pnpm-lock.yaml and queries the registry advisory endpoint. Removing pnpm/action-setup and actions/setup-node saves ~15-20s per PR run.

medium

summary job named 'All Checks Passed' while advisory jobs are excluded

.github/workflows/pr-checks.yml:425

security-audit and migration-lint are continue-on-error and excluded from the summary job's needs array. The name 'All Checks Passed' implies completeness to reviewers and auditors who glance at CI status. Rename to 'Required Checks Passed' or add a comment explaining the intentional advisory exclusions.

medium

GuardDuty org auto-enable runbook should be in a docs/runbooks file, not a stack comment

infra/cdk/src/stacks/security/guardduty-baseline.stack.ts:17

The three-step CLI runbook lives in a block comment. Stack comments should explain 'why'; runbooks explain 'how'. Extract to docs/runbooks/guardduty-org-enrollment.md and reference it by path. This also makes it auditor-linkable from the compliance policy.

medium

Dependabot lacks major-version ignore rules — will generate churn PRs for high-risk packages

.github/dependabot.yml:17

No ignore block for major versions on high-churn packages (Next.js, React, AWS CDK, Drizzle). Major bumps need manual breaking-change evaluation and can't be auto-merged. Adding ignore: [{dependency-name: '*', update-types: ['version-update:semver-major']}] reduces noise; major bumps can be handled deliberately per-package.

low

18 policy skeletons share identical boilerplate — add a _template.md to the directory

docs/compliance/policies/README.md:1

Every policy has the same header table, DRAFT status line, and two-section shape. When policies graduate to a compliance platform they'll be re-entered manually. A _template.md (boilerplate + field instructions) documents the canonical shape and reduces copy-paste errors.

low

AuditTrailStack could use a node validation to make deploy-deferral machine-readable

infra/cdk/src/stacks/audit/audit-trail.stack.ts:40

The stack synthesizes on every cdk synth run but is marked never-deploy. Adding this.node.addValidation({ validate: () => ['DEFERRED: use cdk import to adopt batu-org-trail'] }) makes the intent machine-readable and surfaces a clear error if someone accidentally targets this stack in a deploy command.

low

compliance.md open items duplicate gaps already stated in policy skeletons

.claude/rules/compliance.md:55

Open items 3–7 (MFA/SSO, CFE credential data-flow, GuardDuty member verification, GHAS billing, Linear Compliance project) are also referenced in individual policy skeletons. Two places to update as items close. Pick one owner per finding — link from policies to compliance.md, or vice versa.

info

CodeQL workflow could be a per-job section in pr-checks.yml

.github/workflows/codeql.yml:1

A dedicated file is needed because CodeQL requires security-events:write. However, pr-checks.yml already uses per-job permissions blocks. Adding CodeQL as a job there (with its own permissions:) would reduce workflow file count and make the full PR check surface visible in one place. Minor hygiene only.

History · 6 commits

  1. 2ad7120blockedincremental7H · 14M · 11L2026-07-08 17:33
  2. 26393f2needs attentionincremental5H · 19M · 11L2026-07-08 04:57
  3. 9b22f9fneeds attentionincremental6H · 12M · 9L2026-07-08 03:15
  4. 697438cneeds attentionfull3H · 7M · 6L2026-07-07 20:12
  5. aaec085needs attentionincremental7H · 11M · 9L2026-07-07 17:59current
  6. 6a3693cneeds attentionfull11H · 13M · 6L2026-07-07 17:41