create-filegrc 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +31 -4
- package/package.json +3 -3
- package/src/cli.js +106 -16
- package/src/defaults.js +17 -13
- package/src/index.js +265 -32
- package/template/AGENTS.md +21 -35
- package/template/README.md +24 -20
- package/template/WORKSPACE.md +5 -14
- package/template/data/AGENTS.md +8 -8
- package/template/data/action-items/AGENTS.md +2 -2
- package/template/data/audits/AGENTS.md +3 -3
- package/template/data/documents/document-business-continuity-disaster-recovery.json +0 -1
- package/template/data/documents/document-contractor-policy-acknowledgement.json +0 -1
- package/template/data/documents/document-contractor-training-acknowledgement.json +0 -1
- package/template/data/documents/document-data-retention-schedule.json +0 -1
- package/template/data/documents/document-employee-handbook-acknowledgement.json +0 -1
- package/template/data/documents/document-employee-policy-acknowledgement.json +0 -1
- package/template/data/documents/document-employee-training-acknowledgement.json +0 -1
- package/template/data/documents/document-incident-response-plan.json +0 -1
- package/template/data/documents/document-soc2-management-assertion.json +0 -3
- package/template/data/documents/document-soc2-management-representation.json +0 -3
- package/template/data/documents/document-soc2-period-completeness.json +0 -3
- package/template/data/documents/document-soc2-period-completeness.md +2 -2
- package/template/data/documents/document-soc2-system-description.json +0 -3
- package/template/data/documents/document-soc2-system-description.md +3 -3
- package/template/data/evidence/AGENTS.md +3 -3
- package/template/data/obligation-events/AGENTS.md +1 -1
- package/template/data/people/person-policy-owner.json +1 -1
- package/template/data/policies/policy-anti-bribery-corruption.json +0 -1
- package/template/data/policies/policy-clear-desk-screen.json +0 -1
- package/template/data/policies/policy-data-protection-handling.json +0 -1
- package/template/data/policies/policy-employee-handbook.json +0 -1
- package/template/data/policies/policy-information-security.json +0 -1
- package/template/data/policies/policy-mobile-computing-communications.json +0 -1
- package/template/data/systems/system-filegrc-program-repository.md +3 -3
- package/template/data/workspace.json +3 -3
- package/template/docs/filegrc-audit.png +0 -0
- package/template/docs/filegrc-home.png +0 -0
- package/template/docs/filegrc-social-preview.png +0 -0
- package/template/package.json +2 -2
- package/template-parameters.json +46 -2
- package/template/data/people/person-independent-approver.json +0 -9
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# create-filegrc
|
|
2
2
|
|
|
3
|
-
Create a Git-native
|
|
3
|
+
Create a Git-native filegrc workspace for a SOC 2 program:
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
npx create-filegrc@latest company-grc
|
|
@@ -9,12 +9,39 @@ npm run validate
|
|
|
9
9
|
npm run serve
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Setup asks for the
|
|
12
|
+
Setup asks for the legal organization name, the initial policy owner and their email, a security reporting address, and the program timezone. The generated private project includes a starter Security program, Program Readiness, a policy-driven Work Queue, Policy Events that add linked tasks, later audit preparation, evidence packets, and one dependency: `filegrc`.
|
|
13
|
+
|
|
14
|
+
Creation has two layers. The five-record foundation contains workspace settings, the initial owner, the oversight team, renderer settings, and the filegrc system of record. The default `security` starter adds the framework references, proposed policies, controls, obligations, documents, and training records. Pass `--starter foundation` when you want to stop before selecting a framework.
|
|
15
|
+
|
|
16
|
+
For one noninteractive run, pass company and service fields together or use `--config setup.json`. The optional `setup` object defines the service boundary and management goal after installation. Use `--filegrc-package <directory>` to exercise unpublished local engine changes instead of installing the registry release. This writes a machine-local `file:` dependency, so replace it with a released version before sharing the generated workspace.
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"companyName": "Example Company",
|
|
21
|
+
"policyOwnerName": "Security Owner",
|
|
22
|
+
"policyOwnerEmail": "owner@example.com",
|
|
23
|
+
"securityContactEmail": "security@example.com",
|
|
24
|
+
"timezone": "America/Chicago",
|
|
25
|
+
"starter": "security",
|
|
26
|
+
"setup": {
|
|
27
|
+
"serviceName": "Example Service",
|
|
28
|
+
"boundary": "The production service and supporting infrastructure.",
|
|
29
|
+
"criticality": "high",
|
|
30
|
+
"dataClassification": "Confidential",
|
|
31
|
+
"internetExposed": true,
|
|
32
|
+
"programGoal": "type-2"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npx create-filegrc@latest company-grc --config setup.json
|
|
39
|
+
```
|
|
13
40
|
|
|
14
41
|
Generated workspaces also include layered `AGENTS.md` instructions and model-driven headless commands. `filegrc program-path` gives agents the same six steps, exact page guidance, current status, and next actions shown in the renderer. Agents can define program scope, approve policies, implement controls, test External Evidence, complete policy work, trigger event tasks, and prepare later audit packets through the same domain functions used by the renderer.
|
|
15
42
|
|
|
16
43
|
Git is initialized when needed. The browser can create local commits before a remote is configured.
|
|
17
44
|
|
|
18
|
-
Creation output reports the resolved
|
|
45
|
+
Creation output reports the resolved filegrc version, program timezone, starter record counts, whether installation ran, and whether the target joined an existing Git worktree or received a new repository.
|
|
19
46
|
|
|
20
|
-
Use `npx create-filegrc@latest --help` for
|
|
47
|
+
Use `npx create-filegrc@latest --help` for noninteractive options.
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-filegrc",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Create a
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Create a filegrc workspace for a SOC 2 program",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/Sunpeak-AI/
|
|
8
|
+
"url": "git+https://github.com/Sunpeak-AI/filegrc.git"
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
11
|
"bin": {
|
package/src/cli.js
CHANGED
|
@@ -1,26 +1,67 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import {
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { createFilegrc } from "./index.js";
|
|
3
4
|
|
|
4
5
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
5
|
-
const
|
|
6
|
+
const parsed = parseArgs(argv);
|
|
7
|
+
const config = parsed.options.config ? await readConfig(parsed.options.config) : {};
|
|
8
|
+
const organization = config.organization && typeof config.organization === "object"
|
|
9
|
+
? config.organization
|
|
10
|
+
: {};
|
|
11
|
+
const options = {
|
|
12
|
+
...organization,
|
|
13
|
+
...config,
|
|
14
|
+
...parsed.options,
|
|
15
|
+
setup: {
|
|
16
|
+
...(config.setup || {}),
|
|
17
|
+
...parsed.setup
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
delete options.organization;
|
|
21
|
+
delete options.config;
|
|
22
|
+
delete options.target;
|
|
23
|
+
if (!Object.keys(options.setup).length) delete options.setup;
|
|
24
|
+
const target = parsed.target ?? config.target ?? "filegrc-program";
|
|
6
25
|
if (options.help) return printHelp();
|
|
7
26
|
if (options.version) return printVersion();
|
|
8
|
-
const result = await
|
|
27
|
+
const result = await createFilegrc({ target, ...options });
|
|
9
28
|
console.log(`Created ${result.target}`);
|
|
10
|
-
console.log(
|
|
29
|
+
console.log(
|
|
30
|
+
`filegrc ${result.engineVersion}${result.enginePackage ? ` from ${result.enginePackage}` : ""}: ` +
|
|
31
|
+
`${result.install === "installed" ? "installed" : "installation skipped"}`
|
|
32
|
+
);
|
|
11
33
|
console.log(`Git: ${result.gitMode === "existing-worktree" ? "joined existing worktree" : "initialized new repository"}`);
|
|
34
|
+
console.log(`Timezone: ${result.values.timezone}`);
|
|
35
|
+
for (const stage of result.stages) {
|
|
36
|
+
console.log(`Stage ${stage.id}: ${stage.status}${stage.status === "created" ? ` (${stage.records} records)` : ""}`);
|
|
37
|
+
}
|
|
38
|
+
console.log(
|
|
39
|
+
`${result.setup ? "Workspace after setup" : "Program baseline"}: ${result.resourceCounts.total} records, including ` +
|
|
40
|
+
`${result.resourceCounts.byType.requirement || 0} requirements, ` +
|
|
41
|
+
`${result.resourceCounts.byType.control || 0} controls, and ` +
|
|
42
|
+
`${result.resourceCounts.byType.obligation || 0} obligations.`
|
|
43
|
+
);
|
|
12
44
|
console.log("");
|
|
13
|
-
console.log(` cd ${result.target}`);
|
|
45
|
+
console.log(` cd ${shellQuote(result.target)}`);
|
|
14
46
|
if (options.install === false) console.log(" npm install");
|
|
47
|
+
if (!result.setup) console.log(" npx filegrc setup");
|
|
15
48
|
console.log(" npm run validate");
|
|
16
49
|
console.log(" npm run serve");
|
|
17
50
|
console.log("");
|
|
18
|
-
|
|
51
|
+
if (result.setup) {
|
|
52
|
+
console.log(`Service setup: ${result.setup.system.id} (${result.setup.system.status}), target ${result.setup.target.assuranceGoal}.`);
|
|
53
|
+
}
|
|
54
|
+
console.log("Review the generated records, then commit the approved baseline.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function shellQuote(value) {
|
|
58
|
+
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
19
59
|
}
|
|
20
60
|
|
|
21
61
|
function parseArgs(argv) {
|
|
22
62
|
let target;
|
|
23
63
|
const options = {};
|
|
64
|
+
const setup = {};
|
|
24
65
|
for (let index = 0; index < argv.length; index += 1) {
|
|
25
66
|
const value = argv[index];
|
|
26
67
|
if (!value.startsWith("-") && !target) {
|
|
@@ -34,33 +75,82 @@ function parseArgs(argv) {
|
|
|
34
75
|
else if (name === "yes" || name === "y") options.yes = true;
|
|
35
76
|
else if (name === "force") options.force = true;
|
|
36
77
|
else if (name === "no-install") options.install = false;
|
|
78
|
+
else if (name === "config") options.config = next();
|
|
37
79
|
else if (name === "company-name") options.companyName = next();
|
|
38
80
|
else if (name === "policy-owner-name") options.policyOwnerName = next();
|
|
81
|
+
else if (name === "policy-owner-email") options.policyOwnerEmail = next();
|
|
39
82
|
else if (name === "security-contact-email") options.securityContactEmail = next();
|
|
83
|
+
else if (name === "timezone") options.timezone = next();
|
|
84
|
+
else if (name === "starter") options.starter = next();
|
|
40
85
|
else if (name === "filegrc-version") options.filegrcVersion = next();
|
|
86
|
+
else if (name === "filegrc-package") options.filegrcPackage = next();
|
|
87
|
+
else if (name === "service-name") setup.serviceName = next();
|
|
88
|
+
else if (name === "boundary") setup.boundary = next();
|
|
89
|
+
else if (name === "service-owner") setup.ownerId = next();
|
|
90
|
+
else if (name === "criticality") setup.criticality = next();
|
|
91
|
+
else if (name === "classification") setup.dataClassification = next();
|
|
92
|
+
else if (name === "internet-exposed") setup.internetExposed = booleanOption(next(), "internet-exposed");
|
|
93
|
+
else if (name === "program-goal") setup.programGoal = next();
|
|
94
|
+
else if (name === "setup-draft") setup.draft = true;
|
|
41
95
|
else throw new Error(`Unknown option "${value}".`);
|
|
42
96
|
}
|
|
43
|
-
return { target
|
|
97
|
+
return { target, options, setup };
|
|
44
98
|
}
|
|
45
99
|
|
|
46
100
|
function printHelp() {
|
|
47
101
|
console.log(`create-filegrc [directory] [options]
|
|
48
102
|
|
|
49
|
-
Create a
|
|
103
|
+
Create a filegrc workspace for a SOC 2 program.
|
|
50
104
|
|
|
51
105
|
Options:
|
|
52
|
-
--
|
|
53
|
-
--
|
|
54
|
-
--
|
|
106
|
+
--config <json-file|-> Read creation and optional setup values
|
|
107
|
+
--company-name <legal-name> Legal organization name
|
|
108
|
+
--policy-owner-name <name> Initial policy owner
|
|
109
|
+
--policy-owner-email <email> Policy owner's email address
|
|
110
|
+
--security-contact-email <email> Security reporting address
|
|
111
|
+
--timezone <iana-timezone> Program timezone, such as America/Chicago
|
|
112
|
+
--starter <profile> security (default) or foundation
|
|
55
113
|
--filegrc-version <version> Override resolved engine version
|
|
56
|
-
--
|
|
57
|
-
--
|
|
58
|
-
--
|
|
59
|
-
--
|
|
60
|
-
--
|
|
114
|
+
--filegrc-package <directory> Install an unpublished local filegrc package
|
|
115
|
+
--service-name <name> Complete service setup after creation
|
|
116
|
+
--boundary <description> Initial service boundary
|
|
117
|
+
--service-owner <person-id> Defaults to person-policy-owner
|
|
118
|
+
--criticality <level> low, medium, high, or critical
|
|
119
|
+
--classification <name> Initial data classification
|
|
120
|
+
--internet-exposed <bool> true or false
|
|
121
|
+
--program-goal <goal> none, readiness, type-1, or type-2
|
|
122
|
+
--setup-draft Save combined service setup as a draft
|
|
123
|
+
--no-install Write files and a preliminary lockfile only
|
|
124
|
+
--force Allow a non-empty target without overwriting files
|
|
125
|
+
--yes Use generic prompt defaults
|
|
126
|
+
--version Show the package version
|
|
127
|
+
--help Show this help`);
|
|
61
128
|
}
|
|
62
129
|
|
|
63
130
|
async function printVersion() {
|
|
64
131
|
const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
65
132
|
console.log(packageJson.version);
|
|
66
133
|
}
|
|
134
|
+
|
|
135
|
+
async function readConfig(path) {
|
|
136
|
+
const source = path === "-"
|
|
137
|
+
? await readStdin()
|
|
138
|
+
: await readFile(resolve(String(path)), "utf8");
|
|
139
|
+
const parsed = JSON.parse(source);
|
|
140
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
141
|
+
throw new Error("Creation config must be a JSON object.");
|
|
142
|
+
}
|
|
143
|
+
return parsed;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function readStdin() {
|
|
147
|
+
const chunks = [];
|
|
148
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
149
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function booleanOption(value, name) {
|
|
153
|
+
if (value === "true") return true;
|
|
154
|
+
if (value === "false") return false;
|
|
155
|
+
throw new Error(`--${name} must be true or false.`);
|
|
156
|
+
}
|
package/src/defaults.js
CHANGED
|
@@ -4,7 +4,6 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
4
4
|
const FRAMEWORK_ID = "framework-aicpa-trust-services-criteria";
|
|
5
5
|
const DESCRIPTION_FRAMEWORK_ID = "framework-aicpa-soc2-description-criteria";
|
|
6
6
|
const OWNER_ID = "person-policy-owner";
|
|
7
|
-
const INDEPENDENT_APPROVER_ID = "person-independent-approver";
|
|
8
7
|
const OVERSIGHT_TEAM_ID = "team-security-risk-oversight";
|
|
9
8
|
const INFORMATION_SECURITY_POLICY_ID = "policy-information-security";
|
|
10
9
|
const DATA_POLICY_ID = "policy-data-protection-handling";
|
|
@@ -885,11 +884,11 @@ const obligations = [
|
|
|
885
884
|
}
|
|
886
885
|
];
|
|
887
886
|
|
|
888
|
-
export function baselineRecordPaths() {
|
|
889
|
-
return baselineRecordFiles("2000-01-01").map(({ path }) => path);
|
|
887
|
+
export function baselineRecordPaths(starter = "security") {
|
|
888
|
+
return baselineRecordFiles("2000-01-01", starter).map(({ path }) => path);
|
|
890
889
|
}
|
|
891
890
|
|
|
892
|
-
export function baselineRecordFiles(effectiveDate) {
|
|
891
|
+
export function baselineRecordFiles(effectiveDate, starter = "security") {
|
|
893
892
|
const framework = {
|
|
894
893
|
schemaVersion: 1,
|
|
895
894
|
id: FRAMEWORK_ID,
|
|
@@ -963,21 +962,21 @@ export function baselineRecordFiles(effectiveDate) {
|
|
|
963
962
|
id: OVERSIGHT_TEAM_ID,
|
|
964
963
|
type: "team",
|
|
965
964
|
title: "Security and Risk Oversight",
|
|
966
|
-
status: "
|
|
965
|
+
status: "inactive",
|
|
967
966
|
purpose: "Provide independent oversight of the security program, risk register, incidents, findings, vendor and access reviews, policy changes, exercises, and overdue work. The chair must be separate from the policy owner and people who operate the controls under review.",
|
|
968
|
-
memberIds: [OWNER_ID
|
|
969
|
-
chairIds: [
|
|
967
|
+
memberIds: [OWNER_ID],
|
|
968
|
+
chairIds: [],
|
|
970
969
|
meetingCadence: calendar("month", 3, effectiveDate)
|
|
971
970
|
};
|
|
972
971
|
const programRepository = {
|
|
973
972
|
schemaVersion: 1,
|
|
974
973
|
id: "system-filegrc-program-repository",
|
|
975
974
|
type: "system",
|
|
976
|
-
title: "
|
|
975
|
+
title: "filegrc Program Repository",
|
|
977
976
|
status: "active",
|
|
978
977
|
criticality: "high",
|
|
979
978
|
ownerIds: [OWNER_ID],
|
|
980
|
-
description: "The Git repository that is authoritative for
|
|
979
|
+
description: "The Git repository that is authoritative for filegrc governance records, approvals, exceptions, findings, acknowledgements, evidence indexes, and their revision history.",
|
|
981
980
|
systemKind: "governance-system-of-record",
|
|
982
981
|
environment: "Git repository",
|
|
983
982
|
dataClassification: "Confidential",
|
|
@@ -1007,20 +1006,25 @@ export function baselineRecordFiles(effectiveDate) {
|
|
|
1007
1006
|
policyIds: obligation.policyIds
|
|
1008
1007
|
}));
|
|
1009
1008
|
|
|
1009
|
+
const foundation = [
|
|
1010
|
+
recordFile("systems", programRepository),
|
|
1011
|
+
recordFile("teams", team)
|
|
1012
|
+
];
|
|
1013
|
+
if (starter === "foundation") return foundation;
|
|
1014
|
+
if (starter !== "security") throw new Error(`Unknown starter profile "${starter}".`);
|
|
1010
1015
|
return [
|
|
1011
1016
|
recordFile("frameworks", framework),
|
|
1012
1017
|
recordFile("frameworks", descriptionFramework),
|
|
1013
1018
|
...commonRequirements.map((record) => recordFile("requirements", record)),
|
|
1014
1019
|
...descriptionRequirements.map((record) => recordFile("requirements", record)),
|
|
1015
1020
|
...controlRecords.map((record) => recordFile("controls", record)),
|
|
1016
|
-
|
|
1017
|
-
recordFile("teams", team),
|
|
1021
|
+
...foundation,
|
|
1018
1022
|
...obligationRecords.map((record) => recordFile("obligations", record))
|
|
1019
1023
|
];
|
|
1020
1024
|
}
|
|
1021
1025
|
|
|
1022
|
-
export async function writeBaselineRecords(target, effectiveDate) {
|
|
1023
|
-
for (const { path: relativePath, record } of baselineRecordFiles(effectiveDate)) {
|
|
1026
|
+
export async function writeBaselineRecords(target, effectiveDate, starter = "security") {
|
|
1027
|
+
for (const { path: relativePath, record } of baselineRecordFiles(effectiveDate, starter)) {
|
|
1024
1028
|
const path = join(target, relativePath);
|
|
1025
1029
|
await mkdir(dirname(path), { recursive: true });
|
|
1026
1030
|
await writeFile(path, `${JSON.stringify(record, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|