create-filegrc 0.1.0
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 +21 -0
- package/README.md +18 -0
- package/bin/create-filegrc.js +8 -0
- package/package.json +26 -0
- package/src/cli.js +64 -0
- package/src/defaults.js +1063 -0
- package/src/index.js +252 -0
- package/template/AGENTS.md +227 -0
- package/template/README.md +102 -0
- package/template/data/AGENTS.md +185 -0
- package/template/data/action-items/AGENTS.md +11 -0
- package/template/data/audit-populations/AGENTS.md +13 -0
- package/template/data/audits/AGENTS.md +21 -0
- package/template/data/documents/document-business-continuity-disaster-recovery.json +29 -0
- package/template/data/documents/document-business-continuity-disaster-recovery.md +190 -0
- package/template/data/documents/document-contractor-policy-acknowledgement.json +21 -0
- package/template/data/documents/document-contractor-policy-acknowledgement.md +24 -0
- package/template/data/documents/document-contractor-training-acknowledgement.json +22 -0
- package/template/data/documents/document-contractor-training-acknowledgement.md +20 -0
- package/template/data/documents/document-data-retention-schedule.json +25 -0
- package/template/data/documents/document-data-retention-schedule.md +33 -0
- package/template/data/documents/document-employee-handbook-acknowledgement.json +21 -0
- package/template/data/documents/document-employee-handbook-acknowledgement.md +19 -0
- package/template/data/documents/document-employee-policy-acknowledgement.json +21 -0
- package/template/data/documents/document-employee-policy-acknowledgement.md +24 -0
- package/template/data/documents/document-employee-training-acknowledgement.json +22 -0
- package/template/data/documents/document-employee-training-acknowledgement.md +20 -0
- package/template/data/documents/document-incident-response-plan.json +29 -0
- package/template/data/documents/document-incident-response-plan.md +136 -0
- package/template/data/documents/document-soc2-management-assertion.json +17 -0
- package/template/data/documents/document-soc2-management-assertion.md +24 -0
- package/template/data/documents/document-soc2-management-representation.json +17 -0
- package/template/data/documents/document-soc2-management-representation.md +20 -0
- package/template/data/documents/document-soc2-period-completeness.json +17 -0
- package/template/data/documents/document-soc2-period-completeness.md +32 -0
- package/template/data/documents/document-soc2-system-description.json +17 -0
- package/template/data/documents/document-soc2-system-description.md +65 -0
- package/template/data/evidence/AGENTS.md +30 -0
- package/template/data/obligation-events/AGENTS.md +18 -0
- package/template/data/obligations/AGENTS.md +11 -0
- package/template/data/people/person-independent-approver.json +10 -0
- package/template/data/people/person-policy-owner.json +10 -0
- package/template/data/policies/AGENTS.md +15 -0
- package/template/data/policies/policy-anti-bribery-corruption.json +25 -0
- package/template/data/policies/policy-anti-bribery-corruption.md +87 -0
- package/template/data/policies/policy-clear-desk-screen.json +24 -0
- package/template/data/policies/policy-clear-desk-screen.md +49 -0
- package/template/data/policies/policy-data-protection-handling.json +34 -0
- package/template/data/policies/policy-data-protection-handling.md +130 -0
- package/template/data/policies/policy-employee-handbook.json +31 -0
- package/template/data/policies/policy-employee-handbook.md +161 -0
- package/template/data/policies/policy-information-security.json +61 -0
- package/template/data/policies/policy-information-security.md +233 -0
- package/template/data/policies/policy-mobile-computing-communications.json +28 -0
- package/template/data/policies/policy-mobile-computing-communications.md +74 -0
- package/template/data/renderer.json +7 -0
- package/template/data/risk-assessments/AGENTS.md +16 -0
- package/template/data/systems/system-filegrc-program-repository.md +9 -0
- package/template/data/training/training-anti-bribery-high-risk-roles.json +17 -0
- package/template/data/training/training-anti-bribery-high-risk-roles.md +19 -0
- package/template/data/training/training-privileged-sensitive-roles.json +21 -0
- package/template/data/training/training-privileged-sensitive-roles.md +20 -0
- package/template/data/training/training-secure-development.json +20 -0
- package/template/data/training/training-secure-development.md +22 -0
- package/template/data/training/training-security-awareness.json +28 -0
- package/template/data/training/training-security-awareness.md +192 -0
- package/template/data/workspace.json +27 -0
- package/template/docs/filegrc-audit.png +0 -0
- package/template/docs/filegrc-home.png +0 -0
- package/template/gitignore +4 -0
- package/template/package.json +15 -0
- package/template-parameters.json +34 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { cp, lstat, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { baselineRecordPaths, writeBaselineRecords } from "./defaults.js";
|
|
8
|
+
|
|
9
|
+
const execute = promisify(execFile);
|
|
10
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
11
|
+
const textExtensions = new Set(["", ".json", ".md", ".txt", ".yml", ".yaml", ".gitignore"]);
|
|
12
|
+
|
|
13
|
+
export async function createFileGRC(options = {}) {
|
|
14
|
+
const parameterConfig = JSON.parse(await readFile(join(packageRoot, "template-parameters.json"), "utf8"));
|
|
15
|
+
const target = resolve(options.target ?? "filegrc-program");
|
|
16
|
+
await assertWritableTarget(target, Boolean(options.force));
|
|
17
|
+
if (options.force) await assertNoTemplateCollisions(target);
|
|
18
|
+
|
|
19
|
+
const prompted = await resolvePromptValues(parameterConfig.parameters, options);
|
|
20
|
+
const engineVersion = await resolveFileGRCVersion(options.filegrcVersion);
|
|
21
|
+
const values = {
|
|
22
|
+
...prompted,
|
|
23
|
+
effective_date: options.effectiveDate ?? new Date().toISOString().slice(0, 10),
|
|
24
|
+
project_name: normalizePackageName(basename(target)),
|
|
25
|
+
filegrc_version_range: `^${engineVersion}`
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
await mkdir(target, { recursive: true });
|
|
29
|
+
await copyTemplate(target);
|
|
30
|
+
await renderTemplate(target, parameterConfig, values);
|
|
31
|
+
await writeBaselineRecords(target, values.effective_date);
|
|
32
|
+
|
|
33
|
+
if (options.install !== false) {
|
|
34
|
+
await run("npm", ["install", "--ignore-scripts"], target);
|
|
35
|
+
} else {
|
|
36
|
+
await writeMinimalLockfile(target, values.project_name, values.filegrc_version_range);
|
|
37
|
+
}
|
|
38
|
+
if (!await isInsideGitWorktree(target)) await run("git", ["init"], target);
|
|
39
|
+
return { target, values, engineVersion };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function resolveFileGRCVersion(explicitVersion) {
|
|
43
|
+
if (explicitVersion) return cleanVersion(explicitVersion);
|
|
44
|
+
try {
|
|
45
|
+
const { stdout } = await execute("npm", ["view", "filegrc", "version", "--json"], {
|
|
46
|
+
timeout: 15_000,
|
|
47
|
+
maxBuffer: 100_000
|
|
48
|
+
});
|
|
49
|
+
const parsed = JSON.parse(stdout);
|
|
50
|
+
return cleanVersion(Array.isArray(parsed) ? parsed.at(-1) : parsed);
|
|
51
|
+
} catch {
|
|
52
|
+
const ownPackage = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8"));
|
|
53
|
+
return cleanVersion(ownPackage.version);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function resolvePromptValues(parameters, options) {
|
|
58
|
+
const mapped = {
|
|
59
|
+
company_name: options.companyName,
|
|
60
|
+
policy_owner_name: options.policyOwnerName,
|
|
61
|
+
security_contact_email: options.securityContactEmail
|
|
62
|
+
};
|
|
63
|
+
if (options.yes) {
|
|
64
|
+
mapped.company_name ??= "Example Company";
|
|
65
|
+
mapped.policy_owner_name ??= "Security Owner";
|
|
66
|
+
mapped.security_contact_email ??= "security@example.com";
|
|
67
|
+
}
|
|
68
|
+
for (const key of Object.keys(mapped)) {
|
|
69
|
+
if (mapped[key] !== undefined && mapped[key] !== null) mapped[key] = String(mapped[key]).trim();
|
|
70
|
+
}
|
|
71
|
+
const missing = parameters.filter(({ key, required }) => required && !mapped[key]);
|
|
72
|
+
if (missing.length) {
|
|
73
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
74
|
+
throw new Error(`Missing required values: ${missing.map(({ key }) => key).join(", ")}`);
|
|
75
|
+
}
|
|
76
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
77
|
+
try {
|
|
78
|
+
for (const parameter of missing) {
|
|
79
|
+
let value = "";
|
|
80
|
+
while (!value) value = (await prompt.question(`${parameter.prompt}: `)).trim();
|
|
81
|
+
mapped[parameter.key] = value;
|
|
82
|
+
}
|
|
83
|
+
} finally {
|
|
84
|
+
prompt.close();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const key of ["company_name", "policy_owner_name"]) {
|
|
88
|
+
if (/[\u0000-\u001f\u007f]/.test(mapped[key])) {
|
|
89
|
+
throw new Error(`${key} must be a single line without control characters.`);
|
|
90
|
+
}
|
|
91
|
+
if (mapped[key].length > 200) throw new Error(`${key} must be 200 characters or fewer.`);
|
|
92
|
+
if (/\{\{[a-z0-9_]+\}\}/.test(mapped[key])) {
|
|
93
|
+
throw new Error(`${key} cannot contain template token syntax.`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(mapped.security_contact_email)) {
|
|
97
|
+
throw new Error("Security contact email must be a valid email address.");
|
|
98
|
+
}
|
|
99
|
+
return mapped;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function assertWritableTarget(target, force) {
|
|
103
|
+
try {
|
|
104
|
+
if ((await lstat(target)).isSymbolicLink()) {
|
|
105
|
+
throw new Error(`Target directory must not be a symbolic link: ${target}.`);
|
|
106
|
+
}
|
|
107
|
+
const items = await readdir(target);
|
|
108
|
+
if (items.length && !force) {
|
|
109
|
+
throw new Error(`Target directory is not empty: ${target}. Pass --force to add files without overwriting existing paths.`);
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (error.code !== "ENOENT") throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function assertNoTemplateCollisions(target) {
|
|
117
|
+
const collisions = [];
|
|
118
|
+
for (const destinationPath of [...await templateDestinationPaths(), ...baselineRecordPaths()]) {
|
|
119
|
+
await assertNoSymlinkComponents(target, destinationPath);
|
|
120
|
+
try {
|
|
121
|
+
await lstat(join(target, destinationPath));
|
|
122
|
+
collisions.push(destinationPath);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (error.code !== "ENOENT") throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (collisions.length) {
|
|
128
|
+
throw new Error(`Target contains files create-filegrc would overwrite: ${collisions.slice(0, 5).join(", ")}.`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function assertNoSymlinkComponents(target, relativePath) {
|
|
133
|
+
let current = target;
|
|
134
|
+
for (const segment of relativePath.split(/[\\/]/).filter(Boolean)) {
|
|
135
|
+
current = join(current, segment);
|
|
136
|
+
try {
|
|
137
|
+
if ((await lstat(current)).isSymbolicLink()) {
|
|
138
|
+
throw new Error(`Target contains a symbolic link at ${relative(target, current)}.`);
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error.code === "ENOENT") return;
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function renderTemplate(target, parameterConfig, values) {
|
|
148
|
+
const declared = new Set([
|
|
149
|
+
...parameterConfig.parameters.map(({ key }) => key),
|
|
150
|
+
...parameterConfig.generated.map(({ key }) => key)
|
|
151
|
+
]);
|
|
152
|
+
const files = (await templateDestinationPaths()).map((path) => join(target, path));
|
|
153
|
+
for (const path of files) {
|
|
154
|
+
if (!textExtensions.has(extname(path)) && basename(path) !== ".gitignore") continue;
|
|
155
|
+
let source = await readFile(path, "utf8");
|
|
156
|
+
const jsonFile = extname(path) === ".json";
|
|
157
|
+
source = source.replace(/\{\{([a-z0-9_]+)\}\}/g, (match, token) => {
|
|
158
|
+
if (!declared.has(token)) throw new Error(`Unknown template token "{{${token}}}" in ${path}`);
|
|
159
|
+
if (values[token] === undefined) throw new Error(`No value resolved for template token "{{${token}}}"`);
|
|
160
|
+
return jsonFile ? jsonStringContents(values[token]) : String(values[token]);
|
|
161
|
+
});
|
|
162
|
+
const unresolved = /\{\{([a-z0-9_]+)\}\}/.exec(source);
|
|
163
|
+
if (unresolved) throw new Error(`Unresolved template token "{{${unresolved[1]}}}" in ${path}`);
|
|
164
|
+
await writeFile(path, source, "utf8");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function templateDestinationPaths() {
|
|
169
|
+
const template = join(packageRoot, "template");
|
|
170
|
+
return (await collectFiles(template)).map((source) => {
|
|
171
|
+
const templatePath = relative(template, source);
|
|
172
|
+
return templatePath === "gitignore" ? ".gitignore" : templatePath;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function copyTemplate(target) {
|
|
177
|
+
const template = join(packageRoot, "template");
|
|
178
|
+
for (const source of await collectFiles(template)) {
|
|
179
|
+
const templatePath = relative(template, source);
|
|
180
|
+
const destinationPath = templatePath === "gitignore" ? ".gitignore" : templatePath;
|
|
181
|
+
const destination = join(target, destinationPath);
|
|
182
|
+
await assertNoSymlinkComponents(target, destinationPath);
|
|
183
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
184
|
+
await cp(source, destination, {
|
|
185
|
+
force: false,
|
|
186
|
+
errorOnExist: true,
|
|
187
|
+
preserveTimestamps: false
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function collectFiles(directory) {
|
|
193
|
+
const result = [];
|
|
194
|
+
for (const item of await readdir(directory, { withFileTypes: true })) {
|
|
195
|
+
const path = join(directory, item.name);
|
|
196
|
+
if (item.isDirectory()) result.push(...await collectFiles(path));
|
|
197
|
+
else if (item.isFile()) result.push(path);
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function writeMinimalLockfile(target, name, versionRange) {
|
|
203
|
+
const lock = {
|
|
204
|
+
name,
|
|
205
|
+
version: "0.1.0",
|
|
206
|
+
lockfileVersion: 3,
|
|
207
|
+
requires: true,
|
|
208
|
+
packages: {
|
|
209
|
+
"": {
|
|
210
|
+
name,
|
|
211
|
+
version: "0.1.0",
|
|
212
|
+
dependencies: { filegrc: versionRange }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
await writeFile(join(target, "package-lock.json"), `${JSON.stringify(lock, null, 2)}\n`, "utf8");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function isInsideGitWorktree(target) {
|
|
220
|
+
try {
|
|
221
|
+
await execute("git", ["rev-parse", "--is-inside-work-tree"], { cwd: target });
|
|
222
|
+
return true;
|
|
223
|
+
} catch {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function run(command, args, cwd) {
|
|
229
|
+
try {
|
|
230
|
+
return await execute(command, args, { cwd, maxBuffer: 10_000_000 });
|
|
231
|
+
} catch (error) {
|
|
232
|
+
const detail = error.stderr?.trim() || error.stdout?.trim() || error.message;
|
|
233
|
+
throw new Error(`${command} ${args.join(" ")} failed: ${detail}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function cleanVersion(value) {
|
|
238
|
+
const version = String(value ?? "").trim().replace(/^v/, "");
|
|
239
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
240
|
+
throw new Error(`Could not resolve a valid FileGRC version from "${value}".`);
|
|
241
|
+
}
|
|
242
|
+
return version;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function normalizePackageName(value) {
|
|
246
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
247
|
+
return normalized || "filegrc-program";
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function jsonStringContents(value) {
|
|
251
|
+
return JSON.stringify(String(value)).slice(1, -1);
|
|
252
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# FileGRC SOC 2 Workspace Instructions
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This repository is {{company_name}}’s FileGRC workspace for its SOC 2 program. Engineers and agents maintain the source records under `data/`. The `filegrc` package validates, searches, edits, and renders those files.
|
|
6
|
+
|
|
7
|
+
Using this repository does not establish compliance by itself. Records must match actual practice and evidence must prove that controls operated during the audit period.
|
|
8
|
+
|
|
9
|
+
## Agent quick start
|
|
10
|
+
|
|
11
|
+
Do not guess a resource type, field name, enum value, relationship, or file path. Start every unfamiliar task with the installed model:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npx filegrc guide --json
|
|
15
|
+
npx filegrc guide risk-assessment --json
|
|
16
|
+
npx filegrc list person --json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The first command lists every supported action and record type. The type guide gives the purpose, policy basis, timing, required and conditional fields, current relationship candidates, JSON location, and Markdown slots.
|
|
20
|
+
|
|
21
|
+
For a new record, generate a mutation envelope:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npx filegrc scaffold risk-assessment --title "2026 Annual Risk Assessment" > /tmp/risk-assessment.json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The scaffold contains `{ "record": ..., "content": ... }`, which is the same payload shape used by the renderer. Null values and empty required arrays are deliberate prompts. Replace all of them with facts before creation:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npx filegrc create /tmp/risk-assessment.json
|
|
31
|
+
npx filegrc validate --json
|
|
32
|
+
git diff --check
|
|
33
|
+
git diff
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Read `data/AGENTS.md` before changing records. More specific instructions inside high-risk collections apply in addition to that file.
|
|
37
|
+
|
|
38
|
+
## Working rules
|
|
39
|
+
|
|
40
|
+
- Read `README.md` and run `npm run validate` before broad changes.
|
|
41
|
+
- Treat `data/` as the source of truth. Do not hand-edit `.filegrc/` output.
|
|
42
|
+
- Use UTF-8 JSON for structured records and Markdown for long-form work.
|
|
43
|
+
- Keep one resource in each JSON file.
|
|
44
|
+
- Let the local app generate IDs from each record’s name or title. When editing JSON directly, keep IDs globally unique, human-readable, and lowercase kebab-case.
|
|
45
|
+
- Use ISO 8601 dates and RFC 3339 timestamps.
|
|
46
|
+
- Store relationships as resource IDs.
|
|
47
|
+
- Put policies, plans, charters, procedures, meeting minutes, training, assertions, narratives, templates, and audit responses in Markdown beside their JSON records. FileGRC derives the Markdown name, so records do not contain file paths.
|
|
48
|
+
- Put signed forms, screenshots, third-party reports, and immutable exports behind evidence records. These files may be PDF, image, CSV, or another fixed format.
|
|
49
|
+
- Never fetch an external evidence reference automatically.
|
|
50
|
+
- Do not store secrets, credentials, session data, or personal data that may need to be erased from Git history.
|
|
51
|
+
- Keep the editable local server on loopback or behind trusted authentication. Use the read-only static build for audit sharing.
|
|
52
|
+
|
|
53
|
+
## Git is the audit trail
|
|
54
|
+
|
|
55
|
+
Git supplies file authors, commit timestamps, messages, diffs, and revisions. Do not add fields such as `createdAt`, `updatedAt`, `createdBy`, `updatedBy`, or a second change log.
|
|
56
|
+
|
|
57
|
+
Domain events still need explicit dates. Keep values such as `occurredOn`, `approvedOn`, `reviewedOn`, `completedOn`, and audit-period dates in their records.
|
|
58
|
+
|
|
59
|
+
Make focused commits with messages that explain the reason for the change. The engine never creates commits automatically. Review the workspace diff, then use the commit action on Repository or the Git CLI. The renderer validates the workspace and requires an explicit message before it creates a commit.
|
|
60
|
+
|
|
61
|
+
Pull before starting work when other people or agents may have changed the repository. Without a remote, the browser's Repository page creates a local commit and hides synchronization actions. With a remote, it pulls with rebase, refuses to pull over uncommitted files, and pushes immediately after it creates a commit. Agents and terminal users own Git synchronization and should run `git pull --rebase`, `git commit`, and `git push` directly. Do not create merge commits for routine synchronization.
|
|
62
|
+
|
|
63
|
+
Do not rewrite or remove committed records that explain prior audit periods. Close or retire them. Delete only mistakes and uncommitted drafts.
|
|
64
|
+
|
|
65
|
+
## Data model
|
|
66
|
+
|
|
67
|
+
`data/workspace.json` selects the model through `dataModelVersion`. The installed `filegrc` package owns the authoritative model. Do not copy or invent a local schema.
|
|
68
|
+
|
|
69
|
+
Run these commands when working with records:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
npm run validate
|
|
73
|
+
npx filegrc guide --json
|
|
74
|
+
npx filegrc guide risk
|
|
75
|
+
npx filegrc list risk --json
|
|
76
|
+
npx filegrc get risk-example
|
|
77
|
+
npx filegrc get risk-example --mutation
|
|
78
|
+
npx filegrc references risk-example --json
|
|
79
|
+
npx filegrc describe risk
|
|
80
|
+
npx filegrc search "access review"
|
|
81
|
+
npm run serve
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Prefer existing fields. Put organization-specific values under `extensions` with a namespace owned by {{company_name}}. Add structure only when validation, filtering, relationships, due dates, or audit completeness need it. Variable procedures, interviews, observations, rationale, and detailed results belong in the record's Markdown companion.
|
|
85
|
+
|
|
86
|
+
Never change a resource ID after it is committed. Create a replacement and link the records if identity truly changes.
|
|
87
|
+
|
|
88
|
+
The local app keeps IDs out of the guided form, generates them during creation, and leaves them unchanged when a record is renamed. It presents the core model fields and relationship pickers. Use its advanced JSON section for optional fields and extensions. It rejects a save if the source file changed after the editor opened, so reload and reapply the change instead of overwriting newer work.
|
|
89
|
+
|
|
90
|
+
Headless agents get the same protection by exporting an edit payload with `filegrc get RESOURCE_ID --mutation` and passing that file to `filegrc update`.
|
|
91
|
+
|
|
92
|
+
## Renderer settings and onboarding
|
|
93
|
+
|
|
94
|
+
`data/renderer.json` stores committed renderer preferences. New workspaces set `showOnboarding` to `true`. Completing or skipping onboarding sets it to `false`; the app does not commit that change.
|
|
95
|
+
|
|
96
|
+
Onboarding explains the file and Git workflow, recurring obligations, event checklists, and bulk evidence preparation, then collects the initial service boundary, owner, business criticality, highest data classification, internet exposure, and optional audit objective. It creates or updates a `system` record and may create a planned `audit` record. Treat both as drafts to review against actual scope.
|
|
97
|
+
|
|
98
|
+
The renderer is optional. Agents may set `showOnboarding` to `false` and maintain all records headlessly. Restart onboarding from Repository when useful. Read-only builds never run it.
|
|
99
|
+
|
|
100
|
+
## Starter baseline
|
|
101
|
+
|
|
102
|
+
The generated workspace starts with the SOC 2 Security category:
|
|
103
|
+
|
|
104
|
+
- Active framework records for the 2017 Trust Services Criteria with revised points of focus (2022) and the 2018 SOC 2 Description Criteria with revised implementation guidance (2022)
|
|
105
|
+
- The 33 Common Criteria reference IDs from CC1.1 through CC9.2, without the licensed criteria text
|
|
106
|
+
- The nine Description Criteria reference IDs from DC1 through DC9, without the licensed criteria text
|
|
107
|
+
- Planned controls mapped to those references and the included policies
|
|
108
|
+
- A security and risk oversight team chaired by an external independent reviewer
|
|
109
|
+
- Recurring obligations for the reviews, scans, tests, training, and meetings required by the included policies
|
|
110
|
+
- A default 5x5 risk method and Public, Internal, Confidential, and Restricted data classifications
|
|
111
|
+
|
|
112
|
+
Treat every planned control as a proposal until its owner, scope, operation, and evidence match actual practice. Do not mark a control implemented because a policy describes it. Add Availability, Processing Integrity, Confidentiality, or Privacy criteria only when they are in scope.
|
|
113
|
+
|
|
114
|
+
The recurring obligations mirror the fixed cadences in the starter policies. Update the policy, control, and obligation together when an approved cadence changes. Create separate completion records, such as meetings, reviews, scans, tests, exercises, and attestations, for each period.
|
|
115
|
+
|
|
116
|
+
## Policy work queue
|
|
117
|
+
|
|
118
|
+
Run the same obligation planner used by the web app:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
npx filegrc obligations --json
|
|
122
|
+
npx filegrc obligations --from 2026-01-01 --through 2026-12-31 --complete --json
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
A calendar obligation’s recurrence anchor starts its first allowed cycle. Unless `window` narrows that range, completion is allowed from the cycle start through the day before the next cycle, and the item becomes overdue on the next cycle’s first day. Use **Record work** in the obligation board, or create and link a completion atomically with:
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
npx filegrc complete obligation-id completion-record.json
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Keep prior completion links because the planner matches each dated record to its own period.
|
|
132
|
+
|
|
133
|
+
Event obligations are templates. Do not mark a template complete or replace it for each occurrence. Start a workflow in the obligation board or run:
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
npx filegrc trigger person-started --occurred-on 2026-07-25 --subject person-new-worker --json
|
|
137
|
+
npx filegrc trigger person-ended --occurred-at 2026-07-25T16:30:00-05:00 --subject person-departing-worker --json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The command creates one `obligation-event` and its complete action checklist in a single validated write. Hour-based deadlines require an RFC 3339 event timestamp so an immediate or 24-hour cutoff is exact. Day-based deadlines use the event’s calendar date. Link the requested completion resources and evidence to each action item, then mark the actions done and the event complete. Every generated action has a cutoff. FileGRC applies a 30-day deadline when a custom event obligation omits one.
|
|
141
|
+
|
|
142
|
+
Complete an event action and link its new proof in one validated write:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
npx filegrc complete-action action-item-id completion-record.json --completed-on 2026-07-25
|
|
146
|
+
npx filegrc complete-event obligation-event-id --completed-on 2026-07-25
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
FileGRC rejects a completion resource whose type does not match the obligation. It will close the event only after every action has its requested proof.
|
|
150
|
+
|
|
151
|
+
## Headless Markdown
|
|
152
|
+
|
|
153
|
+
Create and update JSON plus Markdown in one mutation envelope when practical. You can also inspect or replace a companion directly:
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
npx filegrc content risk-assessment risk-assessment-2026 --json
|
|
157
|
+
npx filegrc content risk-assessment risk-assessment-2026 --write updated-assessment.md
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Run `filegrc guide <type>` to get slot names. Policies use `content`, meetings use `agenda` and `minutes`, and implicit long-form work uses `record`. FileGRC derives the path and rejects content that does not belong to the record.
|
|
161
|
+
|
|
162
|
+
## Audit preparation and evidence packets
|
|
163
|
+
|
|
164
|
+
After setting a Type 1 as-of date or Type 2 period, initialize the engagement-specific management work:
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
npx filegrc prepare-audit audit-2026-type-2
|
|
168
|
+
npx filegrc audit-readiness audit-2026-type-2
|
|
169
|
+
npx filegrc audit-readiness audit-2026-type-2 --require-ready --json
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Preparation creates a separate system description, management assertion, and management representation document for the engagement from the local starter templates. Type 2 preparation also creates a period completeness statement and one `audit-population` record for each standard population. It is safe to run again and does not approve documents, mark controls implemented, or create evidence. Do not reuse one completed management document across engagements.
|
|
173
|
+
|
|
174
|
+
Near the end of fieldwork, link a verified fixed-format copy of the signed management representation letter to its engagement-specific document. Date it on or after the Type 1 date or Type 2 period end. A representation that is still marked for later blocks packet delivery.
|
|
175
|
+
|
|
176
|
+
Catalog each authoritative source under Systems and assign its `evidenceSourceKinds`. Name the people who can access its reports and keep extraction instructions in the system's Record Markdown. For each Type 2 population, select one source system and export the exact audit period. Split a population when different systems or queries produce its items. Link a verified `population-export` evidence record that names the same source system and stores the query or report parameters, generation time, timezone, count, completeness check, and accuracy check. A zero count still requires the source export and query. A population linked to an in-scope control cannot be marked not applicable.
|
|
177
|
+
|
|
178
|
+
Every evidence record names its collector. Verified evidence also names its verifier and verification date. Use `sourceSystemId` for system exports, `sourceResourceIds` for FileGRC records, and `sourceCommit` to bind the evidence to repository state.
|
|
179
|
+
|
|
180
|
+
Preview coverage before writing output:
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
npx filegrc evidence-packet --audit audit-2026-type-2 --preview --json
|
|
184
|
+
npx filegrc evidence-packet --audit audit-2026-type-2
|
|
185
|
+
npx filegrc evidence-packet --audit audit-2026-type-2 --preview --require-ready
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The packet includes records explicitly related to the selected engagement, its systems, controls, criteria, policies, evidence, and dependencies. It does not include unrelated dated records from the workspace. A Type 2 packet adds period operating records, recurring obligation occurrences, event workflows, and management population reconciliations. Output includes a control matrix, source-system index, external-evidence delivery index, population index, evidence index, committed historical source versions, and SHA-256 checksums. Output under `.filegrc/evidence-packets/` is derived and must not be hand-edited or committed.
|
|
189
|
+
|
|
190
|
+
Treat a packet as ready for management delivery only when its status is `delivery-ready`, its review list is clear, and its manifest names a clean Git revision. This means FileGRC's management checks passed. It does not mean the engagement team found the evidence sufficient or appropriate. The generator copies raw records, Markdown, and local fixed attachments. It never fetches external references. Reconcile `external-evidence-index.csv` to the auditor portal or other approved delivery system before telling the engagement team that submission is complete.
|
|
191
|
+
|
|
192
|
+
Link a control test to its `audit-population` record when sampling applies. Link item-level sample evidence separately. Management owns population completeness and accuracy. The auditor owns sample selection, independent testing, exception evaluation, and the report opinion. The auditor or publisher also supplies the authoritative criteria and examination guidance. FileGRC stores references and orientation text, not licensed criteria.
|
|
193
|
+
|
|
194
|
+
## Content and approvals
|
|
195
|
+
|
|
196
|
+
The seed policy owner is {{policy_owner_name}} and the reporting address is {{security_contact_email}}. Replace ownership or contacts when responsibilities change.
|
|
197
|
+
|
|
198
|
+
The starter requires an external independent reviewer for SOC 2 oversight. This person must be separate from the policy owner and must not operate the controls they review. They chair Security and Risk Oversight and approve policies and governed documents. A one-person company must appoint a qualified person outside the company before approving the program. Do not use the audit firm for management or approval work without first confirming the firm's independence requirements.
|
|
199
|
+
|
|
200
|
+
Policy and training attestations must identify the exact Git revision of the content that a person acknowledged. Store signatures as evidence attachments and link their evidence IDs from the attestation.
|
|
201
|
+
|
|
202
|
+
Committee and risk meeting minutes are `meeting` resources with a primary Markdown companion. An optional `-agenda.md` companion holds the agenda. Record attendees, decisions, risks discussed, and action item IDs. Keep action tracking in `action-item` records.
|
|
203
|
+
|
|
204
|
+
## Audit evidence
|
|
205
|
+
|
|
206
|
+
A rendered-page evidence capture must identify:
|
|
207
|
+
|
|
208
|
+
- The route and filters shown
|
|
209
|
+
- The audit or evidence period
|
|
210
|
+
- The exact Git commit
|
|
211
|
+
- The capture timestamp and method
|
|
212
|
+
- The source resource IDs
|
|
213
|
+
- The screenshot or fixed export
|
|
214
|
+
|
|
215
|
+
Commit the evidence record and attachment together. Do not claim that a current page proves a prior period unless it is rendered from or bound to the correct revision.
|
|
216
|
+
|
|
217
|
+
## Validation
|
|
218
|
+
|
|
219
|
+
After substantive edits:
|
|
220
|
+
|
|
221
|
+
1. Run `npm run validate`.
|
|
222
|
+
2. Review JSON and Markdown diffs.
|
|
223
|
+
3. Check every new relationship and attachment.
|
|
224
|
+
4. Confirm dates describe the business event, not the edit time.
|
|
225
|
+
5. Run `npm run build` when producing a read-only audit view.
|
|
226
|
+
|
|
227
|
+
Do not loosen validation to make a bad record pass. Fix the record or update the installed engine through its normal versioned model process.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# FileGRC
|
|
2
|
+
|
|
3
|
+
Run a SOC 2 program as files in Git.
|
|
4
|
+
|
|
5
|
+
FileGRC gives a founder-led engineering team one place to adopt policies, track recurring compliance work, respond to company events, and prepare an audit. JSON holds structured records, Markdown holds long-form work, and Git supplies the change history.
|
|
6
|
+
|
|
7
|
+
There is no separate application database. The repository is the program, so engineers and agents can use the same data through the web app, a text editor, or the CLI.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Why it exists
|
|
12
|
+
|
|
13
|
+
SOC 2 work tends to scatter across documents, calendars, tickets, screenshots, and the auditor’s request list. That makes it hard to answer basic questions: What is due? Which policy requires it? What changed during the audit period? Is the evidence complete?
|
|
14
|
+
|
|
15
|
+
FileGRC keeps that work connected:
|
|
16
|
+
|
|
17
|
+
- A starter Security program links criteria references, policies, planned controls, owners, and schedules.
|
|
18
|
+
- The obligation board turns policy timing into upcoming, due, and overdue work.
|
|
19
|
+
- Event checklists cover hiring, departures, vendor changes, incidents, and other policy triggers.
|
|
20
|
+
- Audit Readiness says what management work, source-system exports, and evidence are still missing.
|
|
21
|
+
- The packet builder produces a scoped, indexed delivery with source files, attachments, history, and checksums.
|
|
22
|
+
|
|
23
|
+
The starter content is a proposal, not a claim of compliance. Review every policy and planned control against how your company actually operates before approving it.
|
|
24
|
+
|
|
25
|
+
## Start a workspace
|
|
26
|
+
|
|
27
|
+
You need Node.js 20 or newer and Git.
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npx create-filegrc@latest company-grc
|
|
31
|
+
cd company-grc
|
|
32
|
+
npm run validate
|
|
33
|
+
npm run serve
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Setup asks for the company name, the initial policy owner, and a security contact email. It initializes Git when needed. The first local run then helps define the service boundary, an independent approver, and an optional audit goal.
|
|
37
|
+
|
|
38
|
+
Open the printed local URL. You can commit locally from Repository without configuring a remote. Add a remote when the team is ready to share the workspace, then the browser can pull with rebase and push reviewed commits.
|
|
39
|
+
|
|
40
|
+
## How it works
|
|
41
|
+
|
|
42
|
+
1. Add or edit compliance artifacts under `data/`.
|
|
43
|
+
2. FileGRC validates relationships, renders the current program, and calculates policy work.
|
|
44
|
+
3. Commit focused changes with a message that explains why the records changed.
|
|
45
|
+
4. Git preserves the author, time, message, diff, and prior version.
|
|
46
|
+
5. For an audit, define the Type 1 date or Type 2 period and generate an evidence packet from the selected revision.
|
|
47
|
+
|
|
48
|
+
Long-form policies, procedures, plans, minutes, training, assertions, and audit responses are Markdown companions beside their JSON records. Screenshots, signed acknowledgements, reports, and fixed exports are attachments linked through evidence records.
|
|
49
|
+
|
|
50
|
+
## Run the program
|
|
51
|
+
|
|
52
|
+
Use Overview to follow the audit chain: scope, criteria, policies, controls, operation, and audit. The sidebar follows the same order.
|
|
53
|
+
|
|
54
|
+
Use Obligation Board for recurring work and event checklists. Each item shows its allowed completion window and overdue cutoff, based on the policy that created it. Link a dated completion record and evidence to close the occurrence.
|
|
55
|
+
|
|
56
|
+
Use the resource pages to maintain systems, people, vendors, risks, controls, tests, incidents, training, meetings, and evidence. The question-mark guide on each list explains what the record type is for, which policies call for it, and when to update it.
|
|
57
|
+
|
|
58
|
+
Agents use the same logic headlessly:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
npx filegrc guide risk-assessment --json
|
|
62
|
+
npx filegrc scaffold risk-assessment --title "2026 Annual Risk Assessment"
|
|
63
|
+
npx filegrc list risk --json
|
|
64
|
+
npx filegrc obligations --json
|
|
65
|
+
npx filegrc complete obligation-id completion-record.json
|
|
66
|
+
npx filegrc trigger person-started --occurred-on 2026-07-25 --subject person-id
|
|
67
|
+
npx filegrc complete-action action-item-id completion-record.json --completed-on 2026-07-25
|
|
68
|
+
npx filegrc complete-event obligation-event-id --completed-on 2026-07-25
|
|
69
|
+
npx filegrc search "access review"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`guide` reports the policy context, timing, required fields, valid values, relationship candidates, and Markdown locations for every resource type. `scaffold` produces the same JSON and Markdown mutation shape used by the browser. Read `AGENTS.md` and `data/AGENTS.md` for the full headless workflow.
|
|
73
|
+
|
|
74
|
+
## Prepare the audit
|
|
75
|
+
|
|
76
|
+
Record the audit firm, scope, and exact date or period. Audit Readiness then checks management-owned preparation, including the system description, assertion, policy and control state, source systems, evidence, and Type 2 populations.
|
|
77
|
+
|
|
78
|
+

|
|
79
|
+
|
|
80
|
+
For a Type 2 audit, reconcile each complete period population to its authoritative system after the period closes. A zero-item population still needs its source export and query. FileGRC packages the selected records, Markdown, fixed attachments, historical versions, indexes, and SHA-256 checksums.
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
npx filegrc prepare-audit audit-id
|
|
84
|
+
npx filegrc audit-readiness audit-id --json
|
|
85
|
+
npx filegrc evidence-packet --audit audit-id
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
FileGRC checks management preparation and packet integrity. The independent CPA firm still selects samples, tests controls, evaluates exceptions, decides whether evidence is sufficient, and issues the SOC 2 report.
|
|
89
|
+
|
|
90
|
+
## What belongs elsewhere
|
|
91
|
+
|
|
92
|
+
FileGRC does not replace workforce, identity, source-control, deployment, infrastructure, monitoring, endpoint, backup, vulnerability, training, signature, procurement, contract, or vendor-risk systems.
|
|
93
|
+
|
|
94
|
+
Catalog each authoritative system in FileGRC, record how to export from it, and attach or reference the fixed evidence when Audit Readiness asks for it. The generated external-delivery index identifies files that still need to be supplied through an auditor portal or another approved channel.
|
|
95
|
+
|
|
96
|
+
The starter uses the SOC 2 Security category and does not include licensed criteria text. Add Availability, Processing Integrity, Confidentiality, or Privacy only when they are in scope.
|
|
97
|
+
|
|
98
|
+
## Repository safety
|
|
99
|
+
|
|
100
|
+
The editable server has no authentication and binds to loopback by default. Do not expose it to an untrusted network. Use `npm run build` for a read-only site.
|
|
101
|
+
|
|
102
|
+
Do not put secrets or personal data that may need erasure into Git. Read `AGENTS.md` before broad record changes or automation work.
|