Skip to content
Closed
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
40 changes: 40 additions & 0 deletions packages/agent-mesh/sdks/typescript/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

const tsParser = require('@typescript-eslint/parser');
const tsPlugin = require('@typescript-eslint/eslint-plugin');

/** @type {import('eslint').Linter.FlatConfig[]} */
module.exports = [
{
ignores: ['dist/**', 'coverage/**', 'node_modules/**'],
},
{
files: ['src/**/*.ts', 'tests/**/*.ts'],
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
globals: {
Buffer: 'readonly',
console: 'readonly',
describe: 'readonly',
expect: 'readonly',
beforeEach: 'readonly',
afterEach: 'readonly',
it: 'readonly',
performance: 'readonly',
process: 'readonly',
require: 'readonly',
},
},
plugins: {
'@typescript-eslint': tsPlugin,
},
rules: {
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
},
},
];
23 changes: 12 additions & 11 deletions packages/agent-mesh/sdks/typescript/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/tests'],
testMatch: ['**/*.test.ts'],
collectCoverageFrom: ['src/**/*.ts'],
coverageDirectory: 'coverage',
};
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/tests'],
testMatch: ['**/*.test.ts'],
collectCoverageFrom: ['src/**/*.ts'],
coverageDirectory: 'coverage',
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
};
4 changes: 1 addition & 3 deletions packages/agent-mesh/sdks/typescript/src/audit.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { createHash } from 'crypto';
import { AuditConfig, AuditEntry, LegacyPolicyDecision } from './types';

type PolicyDecision = LegacyPolicyDecision;
import { AuditConfig, AuditEntry } from './types';

const GENESIS_HASH = '0'.repeat(64);

Expand Down
161 changes: 161 additions & 0 deletions packages/agent-mesh/sdks/typescript/src/credential-redactor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import {
CredentialPatternDefinition,
CredentialRedactionResult,
CredentialRedactorConfig,
MCPRedaction,
} from './types';
import { isRecord, truncatePreview, validateRegex } from './mcp-utils';

const DEFAULT_REPLACEMENT = '[REDACTED]';
const SENSITIVE_KEY_PATTERN = /(password|passwd|pwd|secret|token|api[_-]?key|connection.?string|accountkey|sharedaccesssignature|sas)/i;

const BUILTIN_PATTERNS: CredentialPatternDefinition[] = [
{ name: 'openai_key', pattern: /\bsk-[A-Za-z0-9]{16,}\b/g },
{ name: 'github_token', pattern: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g },
{ name: 'aws_access_key', pattern: /\bAKIA[0-9A-Z]{16}\b/g },
{ name: 'bearer_token', pattern: /\bBearer\s+[A-Za-z0-9._\-+/=]{10,}\b/gi },
{
name: 'connection_string',
pattern: /\b(?:AccountKey|SharedAccessKey|Password|Pwd|Secret|ApiKey)\s*=\s*[^;,\s]+/gi,
},
{
name: 'pem_block',
pattern: /-----BEGIN [A-Z0-9 ]+-----[\s\S]*?-----END [A-Z0-9 ]+-----/g,
},
];

interface CompiledPattern {
name: string;
pattern: RegExp;
replacement: string;
}

export class CredentialRedactor {
private readonly replacementText: string;
private readonly redactSensitiveKeys: boolean;
private readonly patterns: CompiledPattern[];

constructor(config: CredentialRedactorConfig = {}) {
this.replacementText = config.replacementText ?? DEFAULT_REPLACEMENT;
this.redactSensitiveKeys = config.redactSensitiveKeys ?? true;
this.patterns = [...BUILTIN_PATTERNS, ...(config.customPatterns ?? [])].map(
(definition) => ({
name: definition.name,
pattern: toGlobalPattern(definition.pattern),
replacement: definition.replacement ?? this.replacementText,
}),
);
}

redactString(value: string, path?: string): CredentialRedactionResult<string> {
let nextValue = value;
const redactions: MCPRedaction[] = [];

for (const pattern of this.patterns) {
pattern.pattern.lastIndex = 0;
const matches = [...nextValue.matchAll(pattern.pattern)];
if (matches.length === 0) {
continue;
}

for (const match of matches) {
redactions.push({
type: pattern.name,
path,
replacement: pattern.replacement,
matchedText: truncatePreview(match[0]),
});
}

nextValue = nextValue.replace(pattern.pattern, pattern.replacement);
}

return {
redacted: nextValue,
redactions,
};
}

redact<T>(value: T): CredentialRedactionResult<T> {
const redactions: MCPRedaction[] = [];
const seen = new WeakMap<object, unknown>();

const redacted = this.redactNode(value, '$', redactions, seen) as T;
return {
redacted,
redactions,
};
}

private redactNode(
value: unknown,
path: string,
redactions: MCPRedaction[],
seen: WeakMap<object, unknown>,
): unknown {
if (typeof value === 'string') {
const result = this.redactString(value, path);
redactions.push(...result.redactions);
return result.redacted;
}

if (Array.isArray(value)) {
return value.map((item, index) =>
this.redactNode(item, `${path}[${index}]`, redactions, seen),
);
}

if (!isRecord(value)) {
return value;
}

if (seen.has(value)) {
return seen.get(value);
}

const clone: Record<string, unknown> = {};
seen.set(value, clone);

for (const [key, current] of Object.entries(value)) {
const childPath = `${path}.${key}`;

if (
this.redactSensitiveKeys
&& SENSITIVE_KEY_PATTERN.test(key)
&& typeof current === 'string'
) {
redactions.push({
type: 'sensitive_key',
path: childPath,
replacement: this.replacementText,
matchedText: truncatePreview(current),
});
clone[key] = this.replacementText;
continue;
}

clone[key] = this.redactNode(current, childPath, redactions, seen);
}

return clone;
}
}

function toGlobalPattern(pattern: RegExp | string): RegExp {
const compiled = pattern instanceof RegExp
? new RegExp(
pattern.source,
pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`,
)
: new RegExp(pattern, 'g');
validateRegex(compiled);

if (pattern instanceof RegExp) {
return compiled;
}

return compiled;
}
4 changes: 2 additions & 2 deletions packages/agent-mesh/sdks/typescript/src/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,15 @@ export class AgentIdentity {
}

/** Suspend this identity temporarily. */
suspend(reason?: string): void {
suspend(_reason?: string): void {
if (this._status === 'revoked') {
throw new Error('Cannot suspend a revoked identity');
}
this._status = 'suspended';
}

/** Revoke this identity permanently. */
revoke(reason?: string): void {
revoke(_reason?: string): void {
this._status = 'revoked';
}

Expand Down
117 changes: 83 additions & 34 deletions packages/agent-mesh/sdks/typescript/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,83 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
export { AgentIdentity, IdentityRegistry, stripKeyPrefix, safeBase64Decode } from './identity';
export { TrustManager } from './trust';
export { PolicyEngine, PolicyConflictResolver } from './policy';
export type { PolicyDecision } from './policy';
export { AuditLogger } from './audit';
export { AgentMeshClient } from './client';
export { GovernanceMetrics } from './metrics';

export {
ConflictResolutionStrategy,
PolicyScope,
} from './types';

export type {
AgentIdentityJSON,
IdentityStatus,
TrustConfig,
TrustScore,
TrustTier,
TrustVerificationResult,
PolicyRule,
Policy,
PolicyAction,
LegacyPolicyDecision,
PolicyDecisionResult,
CandidateDecision,
ResolutionResult,
AuditConfig,
AuditEntry,
AgentMeshConfig,
GovernanceResult,
} from './types';
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
export { AgentIdentity, IdentityRegistry, stripKeyPrefix, safeBase64Decode } from './identity';
export { TrustManager } from './trust';
export { PolicyEngine, PolicyConflictResolver } from './policy';
export type { PolicyDecision } from './policy';
export { AuditLogger } from './audit';
export { AgentMeshClient } from './client';
export { GovernanceMetrics } from './metrics';
export { CredentialRedactor } from './credential-redactor';
export { MCPResponseScanner } from './mcp-response-scanner';
export { InMemoryMCPNonceStore, MCPMessageSigner } from './mcp-message-signer';
export { MCPSessionAuthenticator, InMemoryMCPSessionStore } from './mcp-session-auth';
export { MCPSecurityScanner } from './mcp-security';
export { MCPSlidingRateLimiter } from './mcp-sliding-rate-limiter';
export { InMemoryMCPRateLimitStore } from './mcp-sliding-rate-limiter';
export { MCPGateway, InMemoryMCPAuditSink } from './mcp-gateway';

export {
ApprovalStatus,
MCPThreatType,
MCPSeverity,
} from './types';
export {
ConflictResolutionStrategy,
PolicyScope,
} from './types';

export type {
AgentIdentityJSON,
IdentityStatus,
TrustConfig,
TrustScore,
TrustTier,
TrustVerificationResult,
PolicyRule,
Policy,
PolicyAction,
LegacyPolicyDecision,
PolicyDecisionResult,
CandidateDecision,
ResolutionResult,
AuditConfig,
AuditEntry,
AgentMeshConfig,
GovernanceResult,
MCPMaybePromise,
MCPFindingSeverity,
MCPResponseThreatType,
MCPResponseFinding,
MCPResponseScannerConfig,
MCPResponseScanResult,
CredentialPatternDefinition,
MCPRedaction,
CredentialRedactorConfig,
CredentialRedactionResult,
MCPClock,
MCPSessionTokenPayload,
MCPSessionRecord,
MCPSessionStore,
MCPSessionAuthConfig,
MCPSessionIssueResult,
MCPSessionVerificationResult,
MCPNonceStore,
MCPMessageEnvelope,
MCPMessageSignerConfig,
MCPMessageVerificationResult,
MCPSlidingRateLimitConfig,
MCPSlidingRateLimitResult,
MCPThreat,
ToolFingerprint,
MCPToolDefinition,
MCPScanResult,
MCPScanAuditRecord,
MCPApprovalRequest,
MCPApprovalHandler,
MCPMetricAttributes,
MCPMetricRecorder,
MCPGatewayConfig,
MCPGatewayDecisionResult,
MCPGatewayAuditEntry,
MCPWrappedServerConfig,
} from './types';
Loading
Loading