Skip to content
This repository was archived by the owner on Jul 11, 2026. It is now read-only.

Commit 5ead296

Browse files
SCF API v2026.1 — automated pipeline with 249 framework crosswalks
Automated static JSON API for the Secure Controls Framework (SCF). Parses official SCF Excel releases directly from GitHub, generates 1468 controls, 33 families, and 249 bidirectional framework crosswalks. Includes GitHub Action for automatic updates when new SCF versions are released, and agent-friendly documentation following the llms.txt standard.
0 parents  commit 5ead296

4,938 files changed

Lines changed: 1748537 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths: [".scf-version"]
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
release:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
18+
- name: Read version
19+
id: version
20+
run: |
21+
VERSION=$(cat .scf-version)
22+
echo "tag=$VERSION" >> "$GITHUB_OUTPUT"
23+
echo "Version: $VERSION"
24+
25+
- name: Check if tag exists
26+
id: check
27+
env:
28+
TAG: ${{ steps.version.outputs.tag }}
29+
run: |
30+
if git rev-parse "refs/tags/$TAG" >/dev/null 2>&1; then
31+
echo "skip=true" >> "$GITHUB_OUTPUT"
32+
echo "Tag $TAG already exists, skipping."
33+
else
34+
echo "skip=false" >> "$GITHUB_OUTPUT"
35+
fi
36+
37+
- name: Get stats
38+
if: steps.check.outputs.skip != 'true'
39+
id: stats
40+
run: |
41+
node -e "
42+
const s = require('./docs/api/summary.json');
43+
const lines = [
44+
'controls=' + s.total_controls,
45+
'families=' + s.total_families,
46+
'frameworks=' + s.crosswalk_frameworks.length,
47+
'threats=' + (s.total_threats || 0),
48+
'risks=' + (s.total_risks || 0),
49+
'aos=' + (s.total_assessment_objectives || 0),
50+
'erls=' + (s.total_evidence_requests || 0),
51+
'ccs=' + (s.total_compensating_controls || 0),
52+
'privacy=' + (s.total_privacy_principles || 0),
53+
];
54+
lines.forEach(l => console.log(l));
55+
" >> "$GITHUB_OUTPUT"
56+
57+
- name: Create tag and release
58+
if: steps.check.outputs.skip != 'true'
59+
env:
60+
GH_TOKEN: ${{ github.token }}
61+
TAG: ${{ steps.version.outputs.tag }}
62+
CONTROLS: ${{ steps.stats.outputs.controls }}
63+
FAMILIES: ${{ steps.stats.outputs.families }}
64+
FRAMEWORKS: ${{ steps.stats.outputs.frameworks }}
65+
AOS: ${{ steps.stats.outputs.aos }}
66+
CCS: ${{ steps.stats.outputs.ccs }}
67+
ERLS: ${{ steps.stats.outputs.erls }}
68+
THREATS: ${{ steps.stats.outputs.threats }}
69+
RISKS: ${{ steps.stats.outputs.risks }}
70+
PRIVACY: ${{ steps.stats.outputs.privacy }}
71+
run: |
72+
# Delete existing release if present (idempotent reruns)
73+
gh release delete "$TAG" --yes 2>/dev/null || true
74+
git tag -f "$TAG"
75+
git push --force origin "$TAG"
76+
gh release create "$TAG" \
77+
--title "SCF $TAG" \
78+
--notes "Mirrors [SCF $TAG](https://github.com/securecontrolsframework/securecontrolsframework/releases/tag/$TAG). $CONTROLS controls, $FAMILIES families, $FRAMEWORKS crosswalks, $AOS assessment objectives, $CCS compensating controls, $ERLS evidence requests, $THREATS threats, $RISKS risks, $PRIVACY privacy principles."

.github/workflows/update-scf.yml

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
name: Update SCF
2+
3+
on:
4+
schedule:
5+
# Weekly check: Monday 9:00 UTC
6+
- cron: "0 9 * * 1"
7+
workflow_dispatch:
8+
inputs:
9+
tag:
10+
description: "SCF release tag to update to (leave empty for latest)"
11+
required: false
12+
type: string
13+
force:
14+
description: "Force update even if version matches"
15+
required: false
16+
type: boolean
17+
default: false
18+
19+
permissions:
20+
contents: write
21+
pull-requests: write
22+
23+
jobs:
24+
check-and-update:
25+
runs-on: ubuntu-latest
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v4
29+
30+
- name: Setup Node.js
31+
uses: actions/setup-node@v4
32+
with:
33+
node-version: 20
34+
cache: npm
35+
36+
- name: Install dependencies
37+
run: npm ci
38+
39+
- name: Determine target version
40+
id: version
41+
env:
42+
GH_TOKEN: ${{ github.token }}
43+
INPUT_TAG: ${{ inputs.tag }}
44+
run: |
45+
if [ -n "$INPUT_TAG" ]; then
46+
NEW_TAG="$INPUT_TAG"
47+
else
48+
NEW_TAG=$(gh api repos/securecontrolsframework/securecontrolsframework/releases/latest --jq '.tag_name')
49+
fi
50+
CURRENT=$(cat .scf-version)
51+
echo "new_tag=$NEW_TAG" >> "$GITHUB_OUTPUT"
52+
echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
53+
echo "Current: $CURRENT → Target: $NEW_TAG"
54+
55+
- name: Check if update needed
56+
id: check
57+
env:
58+
NEW_TAG: ${{ steps.version.outputs.new_tag }}
59+
CURRENT: ${{ steps.version.outputs.current }}
60+
FORCE: ${{ inputs.force }}
61+
run: |
62+
if [ "$NEW_TAG" = "$CURRENT" ] && [ "$FORCE" != "true" ]; then
63+
echo "skip=true" >> "$GITHUB_OUTPUT"
64+
echo "Already at version $CURRENT, skipping."
65+
else
66+
echo "skip=false" >> "$GITHUB_OUTPUT"
67+
fi
68+
69+
- name: Parse SCF Excel
70+
if: steps.check.outputs.skip != 'true'
71+
env:
72+
GH_TOKEN: ${{ github.token }}
73+
SCF_TAG: ${{ steps.version.outputs.new_tag }}
74+
run: node scripts/parse-scf-excel.mjs --tag "$SCF_TAG"
75+
76+
- name: Build static API
77+
if: steps.check.outputs.skip != 'true'
78+
run: npm run build
79+
80+
- name: Generate change summary
81+
if: steps.check.outputs.skip != 'true'
82+
id: summary
83+
env:
84+
NEW_TAG: ${{ steps.version.outputs.new_tag }}
85+
CURRENT: ${{ steps.version.outputs.current }}
86+
run: |
87+
CONTROLS=$(node -e "console.log(require('./docs/api/summary.json').total_controls)")
88+
FAMILIES=$(node -e "console.log(require('./docs/api/summary.json').total_families)")
89+
FRAMEWORKS=$(node -e "console.log(require('./docs/api/summary.json').crosswalk_frameworks.length)")
90+
91+
cat > /tmp/pr-body.md <<EOF
92+
## SCF Update: ${CURRENT} → ${NEW_TAG}
93+
94+
### Stats
95+
- **Controls:** ${CONTROLS}
96+
- **Families:** ${FAMILIES}
97+
- **Framework crosswalks:** ${FRAMEWORKS}
98+
99+
### Source
100+
- [SCF ${NEW_TAG} Release](https://github.com/securecontrolsframework/securecontrolsframework/releases/tag/${NEW_TAG})
101+
102+
### Generated by
103+
Automated workflow — [update-scf.yml](.github/workflows/update-scf.yml)
104+
EOF
105+
106+
- name: Remove old data files
107+
if: steps.check.outputs.skip != 'true'
108+
run: |
109+
NEW_SLUG=$(cat .scf-version | tr '.' '-')
110+
# Remove any scf-*.json that isn't the current version or crosswalks
111+
for f in data/scf-*.json; do
112+
base=$(basename "$f")
113+
if [ "$base" != "scf-${NEW_SLUG}.json" ] && [ "$base" != "scf-crosswalks.json" ]; then
114+
echo "Removing old file: $f"
115+
rm "$f"
116+
fi
117+
done
118+
# Remove defunct individual crosswalk files
119+
for f in data/hipaa-scf-crosswalk.json data/gdpr-scf-crosswalk.json data/ccpa-scf-crosswalk.json data/nis2-scf-crosswalk.json; do
120+
[ -f "$f" ] && echo "Removing: $f" && rm "$f"
121+
done
122+
123+
- name: Create Pull Request
124+
if: steps.check.outputs.skip != 'true'
125+
uses: peter-evans/create-pull-request@v7
126+
with:
127+
token: ${{ secrets.GITHUB_TOKEN }}
128+
branch: update-scf/${{ steps.version.outputs.new_tag }}
129+
title: "Update SCF to ${{ steps.version.outputs.new_tag }}"
130+
body-path: /tmp/pr-body.md
131+
commit-message: "Update SCF to ${{ steps.version.outputs.new_tag }}"
132+
labels: automated,scf-update
133+
delete-branch: true

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
.DS_Store
3+
*.xlsx
4+
.firecrawl

.scf-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2026.1

README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# SCF API
2+
3+
Static JSON API for the [Secure Controls Framework](https://securecontrolsframework.com) (SCF).
4+
5+
Parses the official SCF Excel workbook and generates a complete static API — every sheet, every column. Hosted on GitHub Pages, automated with GitHub Actions.
6+
7+
**Live:** https://hackidle.github.io/scf-api/
8+
9+
## What's in it
10+
11+
| Resource | Count | Endpoint |
12+
|---|---|---|
13+
| Controls | 1,468 | `api/controls/{ID}.json` |
14+
| Families | 33 | `api/families/{CODE}.json` |
15+
| Framework crosswalks | 249 | `api/crosswalks/{FW_ID}.json` |
16+
| Assessment objectives | 5,776 | `api/assessment-objectives/{SCF_ID}.json` |
17+
| Compensating controls | 1,305 | `api/compensating-controls/{SCF_ID}.json` |
18+
| Evidence requests | 303 | `api/evidence-requests/{ERL_ID}.json` |
19+
| Privacy principles | 258 | `api/privacy-principles.json` |
20+
| Threats | 41 | `api/threats/{ID}.json` |
21+
| Risks | 39 | `api/risks/{ID}.json` |
22+
23+
Each control includes full metadata: description, assessment question, weight, conformity cadence, PPTDF applicability, NIST CSF function, SCRM focus tiers, SCR-CMM maturity levels (0-5), SCF profiles, possible solutions by org size, risk IDs, threat IDs, evidence request refs, and crosswalk mappings to all 249 frameworks.
24+
25+
## For agents
26+
27+
- [`llms.txt`](https://hackidle.github.io/scf-api/llms.txt) — index
28+
- [`llms-full.txt`](https://hackidle.github.io/scf-api/llms-full.txt) — complete documentation in one file
29+
- [`api/docs.md`](https://hackidle.github.io/scf-api/api/docs.md) — endpoint reference with examples
30+
31+
Follows the [llms.txt standard](https://llmstxt.org).
32+
33+
## Examples
34+
35+
```bash
36+
# All controls
37+
curl https://hackidle.github.io/scf-api/api/controls.json
38+
39+
# Single control with all metadata and crosswalks
40+
curl https://hackidle.github.io/scf-api/api/controls/GOV-01.json
41+
42+
# Framework index (get valid framework IDs)
43+
curl https://hackidle.github.io/scf-api/api/crosswalks.json
44+
45+
# NIST 800-53 crosswalk
46+
curl https://hackidle.github.io/scf-api/api/crosswalks/general-nist-800-53-r5-2.json
47+
48+
# Assessment objectives for a control
49+
curl https://hackidle.github.io/scf-api/api/assessment-objectives/GOV-01.json
50+
51+
# Evidence request
52+
curl https://hackidle.github.io/scf-api/api/evidence-requests/E-GOV-01.json
53+
54+
# Threat catalog
55+
curl https://hackidle.github.io/scf-api/api/threats.json
56+
```
57+
58+
## Rebuilding
59+
60+
```bash
61+
npm ci
62+
npm run parse -- --tag 2026.1 # or --file path/to/workbook.xlsx
63+
npm run build
64+
```
65+
66+
The parser extracts all 10 sheets from the SCF workbook into `data/`, then `npm run build` generates the static API in `docs/`.
67+
68+
## Automation
69+
70+
Two GitHub Actions workflows:
71+
72+
- **`update-scf.yml`** — weekly check for new SCF releases. Downloads the workbook, parses, builds, opens a PR.
73+
- **`release.yml`** — when `.scf-version` changes on main, tags the commit and creates a GitHub release mirroring the SCF version.
74+
75+
## CORS
76+
77+
GitHub Pages doesn't set CORS headers. Works for CLI tools, agents, and server-side code. Browser JS on another origin needs a proxy.
78+
79+
## License
80+
81+
SCF data is from [securecontrolsframework.com](https://securecontrolsframework.com), licensed under CC BY-ND.

0 commit comments

Comments
 (0)