Skip to content

Commit eec0a87

Browse files
committed
P1: post-execution verification - cooperative
1 parent 5b21eef commit eec0a87

6 files changed

Lines changed: 1382 additions & 0 deletions

File tree

src/index.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,46 @@ export {
8080
type VerificationSignalProvider,
8181
} from "./evidence/non-web.js";
8282

83+
// Post-execution verification module
84+
export {
85+
// Evidence types (discriminated union)
86+
type EvidenceType,
87+
type ExecutionEvidence,
88+
type FileEvidence,
89+
type CliEvidence,
90+
type BrowserEvidence,
91+
type HttpEvidence,
92+
type DbEvidence,
93+
type GenericEvidence,
94+
// Core types
95+
type ActualOperation,
96+
type AuthorizedOperation,
97+
type MandateDetails,
98+
type RecordVerificationRequest,
99+
type RecordVerificationResponse,
100+
type VerificationFailureReason,
101+
type VerifyRequest,
102+
type VerifyResult,
103+
type ResourceMatchOptions,
104+
type MandateProvider,
105+
type VerifierOptions,
106+
// Type guards and helpers
107+
getEvidenceType,
108+
isMandateDetails,
109+
isRecordVerificationResponse,
110+
isFileEvidence,
111+
isCliEvidence,
112+
isBrowserEvidence,
113+
isHttpEvidence,
114+
isDbEvidence,
115+
// Comparators
116+
actionsMatch,
117+
normalizeResource,
118+
resourcesMatch,
119+
// Verifier class
120+
Verifier,
121+
} from "./verify/index.js";
122+
83123
// Canonicalization module for reproducible state hashes
84124
export {
85125
// Types

src/verify/comparators.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* Resource comparison functions for post-execution verification.
3+
*
4+
* These functions compare authorized resources against actual resources,
5+
* handling path normalization and glob pattern matching.
6+
*/
7+
8+
import { normalizePath } from "../canonicalization/utils.js";
9+
import { globMatch } from "../policy/matching.js";
10+
11+
/**
12+
* Options for resource matching.
13+
*/
14+
export interface ResourceMatchOptions {
15+
/** Enable glob pattern matching for authorized resource */
16+
allowGlob?: boolean;
17+
}
18+
19+
/**
20+
* Normalize a resource path for comparison.
21+
*
22+
* Applies the following transformations:
23+
* - Expands ~ to home directory
24+
* - Collapses multiple slashes
25+
* - Removes ./ segments
26+
* - Removes trailing slashes
27+
* - Resolves . and ..
28+
*
29+
* @param resource - Resource path to normalize
30+
* @returns Normalized path
31+
*/
32+
export function normalizeResource(resource: string): string {
33+
// Use existing normalizePath for filesystem paths
34+
if (resource.startsWith("/") || resource.startsWith("~") || resource.startsWith(".")) {
35+
let normalized = normalizePath(resource);
36+
// normalizePath doesn't strip trailing slashes, so we do it here
37+
if (normalized.length > 1 && normalized.endsWith("/")) {
38+
normalized = normalized.slice(0, -1);
39+
}
40+
return normalized;
41+
}
42+
43+
// For URLs, handle protocol specially
44+
const urlMatch = resource.match(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)/);
45+
if (urlMatch) {
46+
const protocol = urlMatch[1]; // e.g., "https://"
47+
const rest = resource.slice(protocol.length);
48+
49+
// Normalize the rest (collapse slashes, remove ./, remove trailing /)
50+
const normalized = rest
51+
.replace(/\/+/g, "/") // Collapse multiple slashes
52+
.replace(/\/\.\//g, "/") // Remove ./
53+
.replace(/\/$/g, ""); // Remove trailing slash
54+
55+
return protocol + normalized;
56+
}
57+
58+
// For other non-path resources, do basic cleanup
59+
return resource
60+
.replace(/\/+/g, "/") // Collapse multiple slashes
61+
.replace(/\/\.\//g, "/") // Remove ./
62+
.replace(/\/$/g, ""); // Remove trailing slash
63+
}
64+
65+
/**
66+
* Check if an actual resource matches an authorized resource.
67+
*
68+
* Handles:
69+
* - Path normalization (~ expansion, . and .., etc.)
70+
* - Optional glob pattern matching (* wildcards)
71+
*
72+
* @param authorized - Resource from the mandate (may contain glob patterns)
73+
* @param actual - Resource that was actually accessed
74+
* @param options - Matching options
75+
* @returns True if resources match
76+
*/
77+
export function resourcesMatch(
78+
authorized: string,
79+
actual: string,
80+
options: ResourceMatchOptions = {},
81+
): boolean {
82+
const { allowGlob = true } = options;
83+
84+
// Normalize both resources
85+
const normalizedAuth = normalizeResource(authorized);
86+
const normalizedActual = normalizeResource(actual);
87+
88+
// Exact match after normalization
89+
if (normalizedAuth === normalizedActual) {
90+
return true;
91+
}
92+
93+
// Glob pattern match (if enabled and authorized resource contains wildcards)
94+
if (allowGlob && authorized.includes("*")) {
95+
return globMatch(normalizedActual, authorized);
96+
}
97+
98+
return false;
99+
}
100+
101+
/**
102+
* Check if an actual action matches an authorized action.
103+
*
104+
* Actions are compared case-sensitively after trimming whitespace.
105+
* Supports glob patterns in the authorized action.
106+
*
107+
* @param authorized - Action from the mandate (may contain glob patterns)
108+
* @param actual - Action that was actually performed
109+
* @returns True if actions match
110+
*/
111+
export function actionsMatch(authorized: string, actual: string): boolean {
112+
const normalizedAuth = authorized.trim();
113+
const normalizedActual = actual.trim();
114+
115+
// Exact match
116+
if (normalizedAuth === normalizedActual) {
117+
return true;
118+
}
119+
120+
// Glob pattern match (e.g., "fs.*" matches "fs.read")
121+
if (authorized.includes("*")) {
122+
return globMatch(normalizedActual, authorized);
123+
}
124+
125+
return false;
126+
}

src/verify/index.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Post-execution verification module.
3+
*
4+
* This module provides verification capability to compare actual operations
5+
* against what was authorized via a mandate, detecting unauthorized deviations.
6+
*
7+
* @example
8+
* ```typescript
9+
* import { Verifier } from '@predicatesystems/authority';
10+
*
11+
* const verifier = new Verifier({ baseUrl: 'http://127.0.0.1:8787' });
12+
*
13+
* // After executing an authorized operation
14+
* const result = await verifier.verify({
15+
* mandateId: decision.mandate_id,
16+
* actual: {
17+
* action: 'fs.read',
18+
* resource: '/src/index.ts',
19+
* },
20+
* });
21+
*
22+
* if (!result.verified) {
23+
* console.error('Operation mismatch:', result.reason, result.details);
24+
* }
25+
* ```
26+
*
27+
* @module verify
28+
*/
29+
30+
// Evidence types (discriminated union)
31+
export type {
32+
EvidenceType,
33+
ExecutionEvidence,
34+
FileEvidence,
35+
CliEvidence,
36+
BrowserEvidence,
37+
HttpEvidence,
38+
DbEvidence,
39+
GenericEvidence,
40+
} from "./types.js";
41+
42+
// Core types
43+
export type {
44+
ActualOperation,
45+
AuthorizedOperation,
46+
MandateDetails,
47+
RecordVerificationRequest,
48+
RecordVerificationResponse,
49+
VerificationFailureReason,
50+
VerifyRequest,
51+
VerifyResult,
52+
} from "./types.js";
53+
54+
// Type guards and helpers
55+
export {
56+
getEvidenceType,
57+
isMandateDetails,
58+
isRecordVerificationResponse,
59+
isFileEvidence,
60+
isCliEvidence,
61+
isBrowserEvidence,
62+
isHttpEvidence,
63+
isDbEvidence,
64+
} from "./types.js";
65+
66+
// Comparators
67+
export {
68+
actionsMatch,
69+
normalizeResource,
70+
resourcesMatch,
71+
type ResourceMatchOptions,
72+
} from "./comparators.js";
73+
74+
// Verifier
75+
export { Verifier, type MandateProvider, type VerifierOptions } from "./verifier.js";

0 commit comments

Comments
 (0)