actas 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import {randomBytes} from 'node:crypto';
2
+ import {mailContent} from './mail-content.mjs';
2
3
 
3
4
  // Values never leave this trusted runtime through MCP. Handles expire and consume once.
4
5
  export class PrivateValues {
@@ -16,14 +17,13 @@ export function validateInboxArgs(args){
16
17
  if(args.timeoutMs!==undefined&&(!Number.isInteger(args.timeoutMs)||args.timeoutMs<0||args.timeoutMs>30000))throw Error('Invalid timeout.');return args;
17
18
  }
18
19
  export function verificationValue(message,args,now=Date.now()){
20
+ const data=mailContent(message);if(!data)return;
19
21
  const timestamp=Date.parse(message.timestamp),after=Date.parse(args.after);
20
- if(!message.labels?.includes('received')||!Number.isFinite(timestamp)||timestamp<after||timestamp>now||senderAddress(message.from)!==args.sender.toLowerCase()||typeof message.subject!=='string'||!message.subject.includes(args.subject)||typeof message.text!=='string')return;
22
+ if(!message.labels?.includes('received')||!Number.isFinite(timestamp)||timestamp<after||timestamp>now||senderAddress(data.from)!==args.sender.toLowerCase()||data.subject!==args.subject||typeof data.text!=='string')return;
21
23
  // ActAs wraps received mail as untrusted; match only the body, and only
22
24
  // when ActAs authenticated the sender's domain.
23
25
  if(message.sender_check?.domain_verified!==true)return;
24
- const wrapped=message.untrusted?/^<(untrusted-email-[a-f0-9]+)>\n[\s\S]*?\n\n([\s\S]*)\n<\/\1>$/.exec(message.text):null;
25
- if(message.untrusted&&!wrapped)return;
26
- const body=(wrapped?wrapped[2]:message.text).slice(0,200*1024);
26
+ const body=data.text.slice(0,200*1024);
27
27
  if(args.codePattern){const re=new RegExp(`(?:^|[^A-Za-z0-9])(${args.codePattern})(?=$|[^A-Za-z0-9])`),match=re.exec(body);return match?.[1];}
28
28
  for(const candidate of body.match(/https:\/\/[^\s<>"']+/g)??[]){try{const url=new URL(candidate);if(url.origin===args.linkOrigin&&url.pathname===args.linkPath&&!url.username&&!url.password&&!url.hash)return url.href;}catch{}}
29
29
  }
@@ -2,14 +2,18 @@ import {readFile} from 'node:fs/promises';
2
2
  import {ActAs} from '../scripts/proof-client.mjs';
3
3
  import {loadMappings,selectMapping,callerExecutor} from './caller.mjs';
4
4
  import {helper} from './helper.mjs';
5
+ import {mailContent} from './mail-content.mjs';
5
6
 
6
7
  // Provider messages are untrusted. Only exact owner-assigned senders, recipients,
7
8
  // verification paths and recent received messages can supply a challenge.
8
9
  export function emailChallenge(message,config,principal,since,now=Date.now()) {
10
+ const data=mailContent(message);if(!data)return;
11
+ if(message.untrusted&&message.sender_check?.domain_verified!==true)return;
9
12
  const timestamp=Date.parse(message.timestamp);
10
13
  if(!Number.isFinite(since)||since>now||!Number.isFinite(timestamp)||timestamp<since||timestamp>now||now-timestamp>900000||!message.labels?.includes('received'))return;
11
14
  const address=value=>typeof value==='string'?value.match(/<([^<>]+)>$/)?.[1]||value:undefined;
12
- if(!config.senders?.includes(address(message.from))||!Array.isArray(message.to)||!message.to.some(v=>address(v)===principal)||typeof message.text!=='string')return;
15
+ if(!config.senders?.includes(address(data.from))||!Array.isArray(data.to)||!data.to.some(v=>address(v)===principal)||typeof data.text!=='string')return;
16
+ message={...message,text:data.text};
13
17
  if(config.kind==='code'){
14
18
  // A fixed label avoids arbitrary regex execution and ambiguous numbers in mail.
15
19
  if(typeof config.label!=='string'||!config.label||!Number.isInteger(config.digits)||config.digits<4||config.digits>10)return;
@@ -0,0 +1,9 @@
1
+ // Trusted consumers may parse outside data for display/exact challenge matching.
2
+ // Never return this decoded object directly through an agent-facing mail tool.
3
+ export function mailContent(message){
4
+ if(message?.untrusted!==true)return message;
5
+ if(typeof message.text!=='string')return;
6
+ const match=/^<(untrusted-email-[a-f0-9]{16})>\nOutside email\.[^\n]*\nSender check: [^\n]*\nMetadata: ([^\n]*)\n\n([\s\S]*)\n<\/\1>$/.exec(message.text);
7
+ if(!match)return;
8
+ try{const metadata=JSON.parse(match[2]);if(!metadata||typeof metadata!=='object'||Array.isArray(metadata))return;return {...metadata,text:match[3]};}catch{return;}
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "actas",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "mcpName": "dev.actas/accounts",
5
5
  "type": "module",
6
6
  "description": "Persistent identity, email, saved accounts and scoped credentials for AI agents",