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
11 changes: 11 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ module.exports = {
'**/build/**',
'**/.turbo/**',
'**/coverage/**',
'vantademo/**',
'packages/contracts/artifacts/**',
'github-actions/**/dist/**',
'github-actions/**/scripts/**',
'github-actions/**/src/**/*.js',
'demo.js',
'trustsignal-demo.js',
'bench/**',
'tests/e2e/**',
'packages/contracts/test/**',
'scripts/demo-vanta-terminal.ts',
'src/**',
'scripts/*.js',
'packages/contracts/**/*.js',
Expand Down
18 changes: 18 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ blocked_globs=(
"*.env"
)

blocked_repo_regex='(^docs/compliance/kpmg-|^docs/evidence/|(^|/)audit-output/|(^|/)kpmg-[^/]+)'
blocked_content_regex='(/tmp/kpmg-|kpmg-readiness-)'

failed=0

while IFS= read -r -d '' file; do
Expand All @@ -41,6 +44,21 @@ while IFS= read -r -d '' file; do
;;
esac
done

if [[ "$file" =~ $blocked_repo_regex ]]; then
echo "Blocked private diligence artifact staged: $file" >&2
failed=1
continue
fi

if [[ "$file" == ".githooks/pre-commit" || "$file" == "scripts/tsrepo-private-artifact-audit.sh" ]]; then
continue
fi

if git show ":$file" 2>/dev/null | rg -n "$blocked_content_regex" >/dev/null 2>&1; then
echo "Blocked private diligence reference staged in: $file" >&2
failed=1
fi
done < <(git diff --cached --name-only -z)

if [[ "$failed" -ne 0 ]]; then
Expand Down
60 changes: 56 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,70 @@ node_modules
**/coverage
apps/api/prisma/dev.db
apps/api/prisma/dev.db-journal

# Explicit local env files. Real secrets must never be committed.
.env
.env.*
.env.local
.env.development
.env.development.local
.env.test
.env.test.local
.env.production
.env.production.local
.env.staging
.env.staging.local
apps/api/.env
apps/api/.env.local
apps/api/.env.development
apps/api/.env.development.local
apps/api/.env.test
apps/api/.env.test.local
apps/api/.env.production
apps/api/.env.production.local
apps/api/.env.staging
apps/api/.env.staging.local
apps/web/.env
apps/web/.env.local
apps/web/.env.development
apps/web/.env.development.local
apps/web/.env.test
apps/web/.env.test.local
apps/web/.env.production
apps/web/.env.production.local
apps/web/.env.staging
apps/web/.env.staging.local
packages/*/.env
packages/*/.env.local
packages/*/.env.development
packages/*/.env.development.local
packages/*/.env.test
packages/*/.env.test.local
packages/*/.env.production
packages/*/.env.production.local
packages/*/.env.staging
packages/*/.env.staging.local
**/.env
**/.env.*
**/.env.local
**/.env.development
**/.env.development.local
**/.env.test
Comment on lines 52 to +56

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore wildcard ignore for unlisted .env variants

Replacing wildcard env ignores with a fixed allowlist leaves common secret files like .env.qa, .env.preview, or nested */.env.<custom> unignored. Those files are now eligible to be staged, and the pre-commit glob *.env does not catch them either, which weakens the repository’s secret-leak guardrails. Reintroduce **/.env.* (while keeping !.env.example) to preserve broad protection.

Useful? React with 👍 / 👎.

**/.env.test.local
**/.env.production
**/.env.production.local
**/.env.staging
**/.env.staging.local
!.env.example
!**/.env.example

*.sqlite
**/*.sqlite

# Keys and local env
keys/
keys/*.jwk.json
.env.local
packages/core/registry/registry.private.jwk
.aider*
.vercel
.env*.local
circuits/non_mem_gadget/target/
ml/.venv/
ml/zkml/deed_cnn.pk
Expand All @@ -38,3 +85,8 @@ output/
m1/
coverage/
node_modules/

# Private diligence artifacts
docs/compliance/kpmg-*
docs/evidence/
**/audit-output/
75 changes: 75 additions & 0 deletions apps/api/src/health-endpoints.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { afterEach, describe, expect, it, vi } from 'vitest';

async function buildServerWithDatabaseFailure(message: string, logLines: string[]) {
vi.resetModules();
vi.doMock('./db.js', () => ({
ensureDatabase: vi.fn().mockRejectedValue(new Error(message))
}));

const { buildServer } = await import('./server.js');
return buildServer({
logger: {
level: 'info',
stream: {
write: (line: string) => {
logLines.push(line);
}
}
}
});
}

afterEach(() => {
vi.resetModules();
vi.doUnmock('./db.js');
});

describe('health and status endpoints', () => {
it('redacts raw database init errors from public responses and logs', async () => {
const logLines: string[] = [];
const app = await buildServerWithDatabaseFailure(
'postgresql://db_user:super-secret-password@db.internal.example/trustsignal?sslmode=disable',
logLines
);

try {
const health = await app.inject({
method: 'GET',
url: '/api/v1/health'
});
const status = await app.inject({
method: 'GET',
url: '/api/v1/status'
});

expect(health.statusCode).toBe(200);
expect(status.statusCode).toBe(200);

expect(health.json()).toEqual({
status: 'degraded',
database: {
ready: false,
initError: 'database_initialization_failed'
}
});

const statusBody = status.json() as {
database?: { ready?: boolean; initError?: string | null };
};

expect(statusBody.database?.ready).toBe(false);
expect(statusBody.database?.initError).toBe('database_initialization_failed');

const serialized = JSON.stringify({ health: health.json(), status: statusBody });
const serializedLogs = logLines.join('\n');
expect(serialized).not.toContain('super-secret-password');
expect(serialized).not.toContain('db.internal.example');
expect(serialized).not.toContain('postgresql://');
expect(serializedLogs).not.toContain('super-secret-password');
expect(serializedLogs).not.toContain('db.internal.example');
expect(serializedLogs).not.toContain('postgresql://');
} finally {
await app.close();
}
});
});
48 changes: 45 additions & 3 deletions apps/api/src/lib/v2ReceiptMapper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@

export type RiskBand = "LOW" | "MEDIUM" | "HIGH";
type FraudRiskPayload = {
score?: number;
band?: RiskBand;
signals?: unknown[];
};
type ZkpAttestationPayload = unknown;
type V2VerifyResponse = {
receiptVersion: string;
decision: string;
reasons: string[];
receiptId: string;
receiptHash: string;
receiptSignature?: {
signature: string;
alg: 'EdDSA';
kid: string;
};
proofVerified?: boolean;
anchor: {
status: string;
backend: string;
anchorId?: string;
txHash?: string;
chainId?: string;
anchoredAt?: string;
subjectDigest?: string;
subjectVersion?: string;
};
fraudRisk: {
score: number;
band: RiskBand;
signals: unknown[];
};
zkpAttestation?: ZkpAttestationPayload;
revocation: {
status: "REVOKED" | "ACTIVE";
};
deprecated?: {
riskScore: number;
revoked: boolean;
};
};

function clamp01(x: number): number {
if (Number.isNaN(x)) return 0;
Expand Down Expand Up @@ -32,16 +74,16 @@ export function toV2VerifyResponse(input: {
subjectDigest?: string;
subjectVersion?: string;
};
fraudRisk?: { score?: number; band?: RiskBand; signals?: any[] };
zkpAttestation?: any;
fraudRisk?: FraudRiskPayload;
zkpAttestation?: ZkpAttestationPayload;
revoked?: boolean;
riskScore?: number;
includeDeprecated?: boolean;
}) {
const score = clamp01(input.fraudRisk?.score ?? 0);
const riskBand = input.fraudRisk?.band ?? band(score);

const body: any = {
const body: V2VerifyResponse = {
receiptVersion: "2.0",
decision: input.decision,
reasons: input.reasons ?? [],
Expand Down
Loading
Loading