mattrguerra/meridian-ops-boardmainPrivate on GitHub, published here in full

source-guards.test.ts

Language TypeScript6.3 KB154 lines

apps/dashboard/test/source-guards.test.ts

  1. /**
  2. * Two whole-tree guards against rot that compiles, passes, and looks fine.
  3. *
  4. * The first is the credentials decision, which is a security decision and the
  5. * kind that erodes by accident. The second is source searchability; see its own
  6. * note further down.
  7. *
  8. * Credentials come from the process environment only, exported from the shell
  9. * profile. There is no `.env` reader in this repo and there must not be one: a
  10. * credentials file in the working tree is readable by every tool and agent that
  11. * touches the repo, and a secret that has been read has to be rotated.
  12. *
  13. * The failure this catches is social, not technical: a README line or an error
  14. * message telling people to create a credentials file, shown at the moment they
  15. * are stuck and most likely to comply. Nothing is broken and the advice is
  16. * wrong, which is the hardest combination to notice. A single helpful error
  17. * message is all it takes to put the file back.
  18. *
  19. * It also pins the template to empty values, because a template is exactly where
  20. * a real secret gets pasted "just for a minute".
  21. */
  22. import { readdir, readFile, stat } from 'node:fs/promises'
  23. import { join, relative } from 'node:path'
  24. import { fileURLToPath } from 'node:url'
  25. import { describe, expect, it } from 'vitest'
  26. const ROOT = fileURLToPath(new URL('../../../', import.meta.url))
  27. const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'coverage', '.wrangler', '__pycache__'])
  28. /**
  29. * This file is skipped because it necessarily contains every pattern it looks
  30. * for.
  31. */
  32. const SKIP_FILES = new Set([
  33. 'apps/dashboard/test/source-guards.test.ts',
  34. ])
  35. // A file type the walk does not know about is a file type both guards below silently stop
  36. // covering, which is this test's own failure mode.
  37. const EXTS = ['.ts', '.py', '.md', '.sh', '.json', '.jsonc', '.sql']
  38. async function sources(): Promise<{ path: string; text: string }[]> {
  39. const out: { path: string; text: string }[] = []
  40. async function walk(dir: string): Promise<void> {
  41. for (const entry of await readdir(dir)) {
  42. if (SKIP_DIRS.has(entry)) continue
  43. const full = join(dir, entry)
  44. const rel = relative(ROOT, full)
  45. if ((await stat(full)).isDirectory()) {
  46. await walk(full)
  47. continue
  48. }
  49. if (!EXTS.some((e) => entry.endsWith(e))) continue
  50. if (SKIP_FILES.has(rel) || entry === 'pnpm-lock.yaml') continue
  51. out.push({ path: rel, text: await readFile(full, 'utf8') })
  52. }
  53. }
  54. await walk(ROOT)
  55. return out
  56. }
  57. /**
  58. * Only unambiguous instructions and mechanisms. Deliberately does not match the
  59. * many correct sentences saying there is no such file; the point is to catch
  60. * something telling a person or a build to use one.
  61. */
  62. const FORBIDDEN: { pattern: RegExp; why: string }[] = [
  63. { pattern: /\bdotenv\b/i, why: 'a dotenv dependency reintroduces the file' },
  64. { pattern: /--env-file/, why: 'a script loading an env file' },
  65. { pattern: /\.env\.example/, why: 'the deleted template; credentials.example.sh replaced it' },
  66. { pattern: /\b(cp|copy|mv|touch)\s+\.env/i, why: 'an instruction to create the file' },
  67. { pattern: /source\s+\.env/, why: 'an instruction to source the file' },
  68. { pattern: /load_dotenv/, why: 'a Python dotenv loader' },
  69. ]
  70. describe('nothing tells anyone to create a .env', () => {
  71. it('has no instruction or loader anywhere in the tree', async () => {
  72. const hits: string[] = []
  73. for (const { path, text } of await sources()) {
  74. for (const { pattern, why } of FORBIDDEN) {
  75. const lines = text.split('\n')
  76. lines.forEach((line, i) => {
  77. if (pattern.test(line)) hits.push(`${path}:${i + 1}: ${why}\n ${line.trim()}`)
  78. })
  79. }
  80. }
  81. expect(hits, `\n${hits.join('\n')}\n`).toEqual([])
  82. })
  83. it('scanned a believable number of files, so a broken walk cannot pass silently', async () => {
  84. // An empty scan would satisfy the test above without checking anything.
  85. const files = await sources()
  86. expect(files.length).toBeGreaterThan(40)
  87. expect(files.map((f) => f.path)).toContain('README.md')
  88. })
  89. })
  90. describe('the credentials template carries no secrets', () => {
  91. const template = () => readFile(join(ROOT, 'credentials.example.sh'), 'utf8')
  92. it('exists, because it is what the docs now point at', async () => {
  93. expect((await template()).length).toBeGreaterThan(0)
  94. })
  95. it('leaves every secret empty', async () => {
  96. // A model name and the public API URL are not secrets and may carry a value.
  97. const NOT_SECRET = new Set(['ANTHROPIC_MODEL', 'ANTHROPIC_BASE_URL'])
  98. for (const line of (await template()).split('\n')) {
  99. const m = /^export\s+([A-Z0-9_]+)=(.*)$/.exec(line.trim())
  100. if (!m || NOT_SECRET.has(m[1]!)) continue
  101. expect(m[2]!.replace(/["']/g, ''), `${m[1]} has a value in the template`).toBe('')
  102. }
  103. })
  104. it('names every credential the code actually reads', async () => {
  105. const text = await template()
  106. for (const key of ['NINETY_TOKEN', 'BITRIX_WEBHOOK_URL', 'PLINTH_API_KEY', 'ANTHROPIC_API_KEY']) {
  107. expect(text, `${key} is missing from the template`).toContain(key)
  108. }
  109. })
  110. })
  111. /**
  112. * A literal 0x00 byte in a source file (say, a separator in a composite Map key)
  113. * compiles happily, but `file` then classifies the source as binary and grep
  114. * prints nothing for it: no error, no "binary file matches", just silence. That
  115. * is a silent false negative on every search of that file, for a person or an
  116. * agent. Write the character as an escape; this keeps every source file that
  117. * way.
  118. */
  119. describe('source files stay searchable', () => {
  120. it('has no raw control byte anywhere in source', async () => {
  121. // Tabs, newlines and carriage returns are ordinary whitespace. Everything
  122. // else below 0x20, plus DEL, makes grep treat the whole file as binary.
  123. const hits: string[] = []
  124. for (const { path, text } of await sources()) {
  125. for (let i = 0; i < text.length; i++) {
  126. const c = text.charCodeAt(i)
  127. const ok = c === 0x09 || c === 0x0a || c === 0x0d
  128. if ((c < 0x20 && !ok) || c === 0x7f) {
  129. const line = text.slice(0, i).split('\n').length
  130. hits.push(
  131. `${path}:${line}: raw 0x${c.toString(16).padStart(2, '0')}. ` +
  132. `Write it as an escape; a raw one makes grep silently skip the file.`,
  133. )
  134. break
  135. }
  136. }
  137. }
  138. expect(hits, `\n${hits.join('\n')}\n`).toEqual([])
  139. })
  140. })