Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions packages/telemetry/test/telemetry-v1.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,13 @@ test('telemetry rejects proxies that fail during reflection without exposing tra
assert.deepEqual(sanitizeTelemetryAttributesV1(hostileAttributes), {});
assert.throws(
() => assertSafeTelemetryAttributesV1(hostileAttributes),
UnsafeTelemetryAttributeErrorV1,
(error) => {
assert.ok(error instanceof UnsafeTelemetryAttributeErrorV1);
assert.equal(error.key, 'unreadable');
assert.equal(error.message, 'Telemetry attribute is not allowed: unreadable');
assert.doesNotMatch(error.message, /attribute trap cause/u);
return true;
},
);

const hostileHeaders = new Proxy(
Expand All @@ -108,7 +114,14 @@ test('telemetry rejects proxies that fail during reflection without exposing tra
},
},
);
assert.throws(() => correlationFromHeadersV1(hostileHeaders), /Unreadable telemetry/u);
assert.throws(
() => correlationFromHeadersV1(hostileHeaders),
(error) => {
assert.equal(error.message, 'Unreadable telemetry x-correlation-id header');
assert.doesNotMatch(error.message, /header trap cause/u);
return true;
},
);
});

test('correlation headers round-trip without accepting malformed identifiers', () => {
Expand Down Expand Up @@ -146,7 +159,10 @@ test('correlation headers round-trip without accepting malformed identifiers', (
assert.throws(() => correlationFromHeadersV1({}));
assert.throws(
() => correlationFromHeadersV1({ 'x-correlation-id': 1 }),
/Unreadable telemetry x-correlation-id header/u,
(error) => {
assert.equal(error.message, 'Unreadable telemetry x-correlation-id header');
return true;
},
);
assert.throws(() =>
correlationFromHeadersV1({ 'x-correlation-id': [correlationId, correlationId] }),
Expand Down
34 changes: 1 addition & 33 deletions tools/repo-cli/src/check-aws-infrastructure.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import os from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { balancedBlocks } from './terraform-safety.mjs';

const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
const infrastructureRoot = path.join(repositoryRoot, 'infrastructure', 'aws');
Expand Down Expand Up @@ -32,39 +33,6 @@ const allTerraform = requiredFiles
.map((relativePath) => readFileSync(path.join(infrastructureRoot, relativePath), 'utf8'))
.join('\n');

function balancedBlocks(text, keyword) {
const blocks = [];
const startPattern = new RegExp(`\\b${keyword}\\s*\\{`, 'g');
for (const match of text.matchAll(startPattern)) {
const openingBrace = text.indexOf('{', match.index);
let depth = 0;
let quoted = false;
let escaped = false;
for (let index = openingBrace; index < text.length; index += 1) {
const character = text[index];
if (quoted) {
if (escaped) escaped = false;
else if (character === '\\') escaped = true;
else if (character === '"') quoted = false;
continue;
}
if (character === '"') {
quoted = true;
continue;
}
if (character === '{') depth += 1;
if (character === '}') {
depth -= 1;
if (depth === 0) {
blocks.push(text.slice(openingBrace, index + 1));
break;
}
}
}
}
return blocks;
}

for (const requiredText of [
'ap-southeast-1',
'hashicorp/aws',
Expand Down
13 changes: 5 additions & 8 deletions tools/repo-cli/src/check-ci-policy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,10 @@ function walk(value, visit) {
for (const child of Object.values(value)) walk(child, visit);
}

function containsText(value, expected) {
function containsRunText(value, expected) {
let found = false;
walk(value, (node) => {
if (
Object.values(node).some((child) => typeof child === 'string' && child.includes(expected))
) {
found = true;
}
if (typeof node.run === 'string' && node.run.includes(expected)) found = true;
});
return found;
}
Expand Down Expand Up @@ -128,13 +124,14 @@ export function checkCiPolicy(root = process.cwd()) {
'check-container-policy.mjs',
'generate-sbom.mjs',
]) {
if (!containsText(security, required)) throw new Error(`security.yml is missing ${required}`);
if (!containsRunText(security, required))
throw new Error(`security.yml is missing ${required}`);
}
const release = workflows['release.yml'];
if (!isRecord(release.permissions) || release.permissions['id-token'] !== 'write') {
throw new Error('release.yml must request OIDC id-token permission explicitly');
}
if (!containsText(release, 'generate-provenance.mjs')) {
if (!containsRunText(release, 'generate-provenance.mjs')) {
throw new Error('release.yml must generate a provenance record');
}
let hasReleaseEnvironment = false;
Expand Down
139 changes: 139 additions & 0 deletions tools/repo-cli/src/terraform-safety.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
function maskTerraformNonCode(text) {
const masked = text.split('');
let quoted = false;
let escaped = false;
let lineComment = false;
let blockComment = false;
let heredoc = undefined;
let lineStart = true;

const mask = (index) => {
if (masked[index] !== '\n') masked[index] = ' ';
};

for (let index = 0; index < text.length; index += 1) {
const character = text[index];
const next = text[index + 1];

if (heredoc !== undefined) {
if (lineStart) {
const lineEnd = text.indexOf('\n', index) === -1 ? text.length : text.indexOf('\n', index);
const rawLine = text.slice(index, lineEnd).replace(/\r$/u, '');
const candidate = heredoc.allowIndent ? rawLine.trim() : rawLine;
for (let position = index; position < lineEnd; position += 1) mask(position);
if (candidate === heredoc.delimiter) heredoc = undefined;
lineStart = false;
index = lineEnd - 1;
continue;
}
mask(index);
if (character === '\n') lineStart = true;
continue;
}

if (lineComment) {
mask(index);
if (character === '\n') {
lineComment = false;
lineStart = true;
}
continue;
}

if (blockComment) {
mask(index);
if (character === '*' && next === '/') {
mask(index + 1);
index += 1;
blockComment = false;
}
lineStart = character === '\n';
continue;
}

if (quoted) {
if (escaped) escaped = false;
else if (character === '\\') escaped = true;
else if (character === '"') quoted = false;
if (character === '\n') lineStart = true;
else lineStart = false;
continue;
}

if (character === '"') {
quoted = true;
lineStart = false;
continue;
}
if (character === '#') {
lineComment = true;
mask(index);
lineStart = false;
continue;
}
if (character === '/' && next === '/') {
lineComment = true;
mask(index);
mask(index + 1);
index += 1;
lineStart = false;
continue;
}
if (character === '/' && next === '*') {
blockComment = true;
mask(index);
mask(index + 1);
index += 1;
lineStart = false;
continue;
}
if (character === '<' && next === '<') {
const match = text.slice(index).match(/^<<(-?)([A-Za-z_][A-Za-z0-9_-]*)/u);
if (match) {
for (let position = index; position < index + match[0].length; position += 1) {
mask(position);
}
heredoc = { allowIndent: match[1] === '-', delimiter: match[2] };
index += match[0].length - 1;
lineStart = false;
continue;
}
}
lineStart = character === '\n';
}
return masked.join('');
}

export function balancedBlocks(text, keyword) {
const masked = maskTerraformNonCode(text);
const blocks = [];
const startPattern = new RegExp(`\\b${keyword}\\s*\\{`, 'g');
for (const match of masked.matchAll(startPattern)) {
const openingBrace = masked.indexOf('{', match.index);
let depth = 0;
let quoted = false;
let escaped = false;
for (let index = openingBrace; index < masked.length; index += 1) {
const character = masked[index];
if (quoted) {
if (escaped) escaped = false;
else if (character === '\\') escaped = true;
else if (character === '"') quoted = false;
continue;
}
if (character === '"') {
quoted = true;
continue;
}
if (character === '{') depth += 1;
if (character === '}') {
depth -= 1;
if (depth === 0) {
blocks.push(masked.slice(openingBrace, index + 1));
break;
}
}
}
}
return blocks;
}
45 changes: 45 additions & 0 deletions tools/repo-cli/test/ci-policy.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,48 @@ test('CI policy rejects artifact steps without missing-output failure', () => {
fs.writeFileSync(path.join(root, '.github/workflows', name), text);
assert.throws(() => checkCiPolicy(root), /artifact uploads must fail/u);
});

test('CI policy requires security and release commands in run values', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'databreeze-ci-command-fields-'));
fs.mkdirSync(path.join(root, '.github/workflows'), { recursive: true });
const checkout = 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683';
const checkedOutJob = [
'jobs:',
' check:',
' runs-on: ubuntu-24.04',
' timeout-minutes: 10',
' steps:',
` - uses: ${checkout}`,
' with:',
' persist-credentials: false',
].join('\n');
const security = [
'name: "pnpm audit check-secret-patterns.mjs check-license-policy.mjs check-container-policy.mjs generate-sbom.mjs"',
'permissions:',
' contents: read',
checkedOutJob,
].join('\n');
const release = [
'name: "generate-provenance.mjs"',
'permissions:',
' contents: read',
' id-token: write',
'jobs:',
' release:',
' runs-on: ubuntu-24.04',
' timeout-minutes: 10',
' environment: release',
' steps:',
` - uses: ${checkout}`,
' with:',
' persist-credentials: false',
].join('\n');
const quality = ['name: q', 'permissions:', ' contents: read', checkedOutJob].join('\n');
for (const [name, text] of Object.entries({
'quality.yml': quality,
'security.yml': security,
'release.yml': release,
}))
fs.writeFileSync(path.join(root, '.github/workflows', name), text);
assert.throws(() => checkCiPolicy(root), /security\.yml is missing pnpm audit/u);
});
41 changes: 41 additions & 0 deletions tools/repo-cli/test/terraform-safety.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import assert from 'node:assert/strict';
import test from 'node:test';

import { balancedBlocks } from '../src/terraform-safety.mjs';

test('Terraform block scanning ignores comments, heredocs, and braces in strings', () => {
const source = `
# ingress { cidr_blocks = ["0.0.0.0/0"] }
/* ingress { cidr_blocks = ["0.0.0.0/0"] } */
locals {
description = <<-EOT
ingress {
cidr_blocks = ["0.0.0.0/0"]
}
braces: { };
EOT
}
ingress {
description = "literal } brace"
${' '.repeat(500)}
cidr_blocks = ["10.0.0.0/8"]
}
`;
const blocks = balancedBlocks(source, 'ingress');
assert.equal(blocks.length, 1);
assert.match(blocks[0], /10\.0\.0\.0\/8/u);
assert.doesNotMatch(blocks[0], /0\.0\.0\.0\/0/u);
});

test('Terraform principal scanning ignores commented wildcard identifiers', () => {
const source = `
/* principals { identifiers = ["*"] } */
principals {
type = "Service"
identifiers = ["example.amazonaws.com"]
}
`;
const blocks = balancedBlocks(source, 'principals');
assert.equal(blocks.length, 1);
assert.doesNotMatch(blocks[0], /identifiers\s*=\s*\[[^\]]*"\*"/u);
});
Loading