feat/sec-base
needs attentionviewing older commitaaec085 · incrementalpre-PRreviewed 2026-07-07 17:59 UTC7H · 11M · 9L · 5I- 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)
- 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/+122−42infra/cdk/src/stacks/+46−8.claude/rules/+63−0docs/compliance/policies/+351−0.branch/+57−120
- 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.
Findings · 29
correctness2
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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).
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.
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
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.
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.
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.
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
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' }}'.
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.
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.
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.
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.
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.
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.
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.
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
- 2ad7120blockedincremental7H · 14M · 11L2026-07-08 17:33
- 26393f2needs attentionincremental5H · 19M · 11L2026-07-08 04:57
- 9b22f9fneeds attentionincremental6H · 12M · 9L2026-07-08 03:15
- 697438cneeds attentionfull3H · 7M · 6L2026-07-07 20:12
- aaec085needs attentionincremental7H · 11M · 9L2026-07-07 17:59current
- 6a3693cneeds attentionfull11H · 13M · 6L2026-07-07 17:41