anyray-connect 0.11.165 → 0.11.166

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.
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ var BR="http://localhost:8787",Ga="anyray-placeholder",Ee=e=>{let t=e?.trim();re
22
22
  <key>NSHumanReadableCopyright</key><string>Othentic Labs LTD</string>
23
23
  </dict>
24
24
  </plist>
25
- `,cT="/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister",lT=e=>{try{sT(cT,["-f",e],{stdio:"ignore",timeout:1e4})}catch{}},mw=async(e={})=>{if((e.os??uw())!=="darwin")return{ok:!0,messages:[]};let n=e.home??zu(),r=e.binaryPath??qn();if(Ue(r))return{ok:!1,messages:[`Skipped the Login Items identity app \u2014 anyray-connect is running from a temporary path (${r}).`]};if(es())return{ok:!1,messages:["Skipped the Login Items identity app \u2014 HOME is redirected away from the real account."]};let o=fw(n),a=fc(o,"Contents","MacOS"),s=fc(a,hw),i=e.version??"0.0.0";try{await pw(o,{recursive:!0,force:!0}),await nT(a,{recursive:!0}),await rT(fc(o,"Contents","Info.plist"),iT(i),"utf8");try{await oT(r,s)}catch{await aT(r,s)}return(e.register??lT)(o),{ok:!0,messages:[`Registered ${ts} as the Login Items identity.`]}}catch(c){return{ok:!1,messages:[`Could not write the Login Items identity app (${c?.name??"Error"}). Background items will show the signing certificate name instead.`]}}},yw=async(e={})=>{if((e.os??uw())!=="darwin")return{ok:!0,messages:[]};if(es())return{ok:!0,messages:[]};let n=fw(e.home??zu());try{return await pw(n,{recursive:!0,force:!0}),{ok:!0,messages:[`Removed ${ts}.app.`]}}catch(r){return{ok:!1,messages:[`Could not remove the Login Items identity app (${r?.name??"Error"}).`]}}};var Ae="0.11.165";var tn=(e=process.env)=>e.CODESPACES==="true"||e.CODESPACE_NAME?"codespaces":e.REMOTE_CONTAINERS==="true"||e.REMOTE_CONTAINERS_IPC||e.DEVCONTAINER?"devcontainer":e.SSH_CONNECTION||e.SSH_TTY?"ssh":null,$r=(e=process.env)=>tn(e)!==null,kn=e=>e==="codespaces"||e==="devcontainer",Oo=e=>{switch(e){case"codespaces":return"a GitHub Codespace";case"devcontainer":return"a dev container";case"ssh":return"an SSH session"}};var gw=()=>Br(wc(),".anyray","logs"),ep=()=>Br(gw(),"connect-refresh.log"),gc=1800,bc="ai.anyray.connect.refresh",os="anyray-refresh",np="\\AnyrayConnectKeyRefresh",Ac=bc,as=os,rs=np,fT=String(gc/60),rp=(e,t)=>{let n=pT(e,t,{stdio:"ignore"});return{ok:!n.error&&n.status===0}},op=()=>Br(wc(),"Library","LaunchAgents",`${Ac}.plist`),ap=()=>Br(wc(),".config","systemd","user"),ww=()=>Br(ap(),`${as}.service`),yc=()=>Br(ap(),`${as}.timer`),mc=e=>e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),Cc=()=>zn(jn),hT=(e=Cc())=>{let{command:t,args:n}=e,r=[t,...n].map(o=>` <string>${mc(o)}</string>`).join(`
25
+ `,cT="/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister",lT=e=>{try{sT(cT,["-f",e],{stdio:"ignore",timeout:1e4})}catch{}},mw=async(e={})=>{if((e.os??uw())!=="darwin")return{ok:!0,messages:[]};let n=e.home??zu(),r=e.binaryPath??qn();if(Ue(r))return{ok:!1,messages:[`Skipped the Login Items identity app \u2014 anyray-connect is running from a temporary path (${r}).`]};if(es())return{ok:!1,messages:["Skipped the Login Items identity app \u2014 HOME is redirected away from the real account."]};let o=fw(n),a=fc(o,"Contents","MacOS"),s=fc(a,hw),i=e.version??"0.0.0";try{await pw(o,{recursive:!0,force:!0}),await nT(a,{recursive:!0}),await rT(fc(o,"Contents","Info.plist"),iT(i),"utf8");try{await oT(r,s)}catch{await aT(r,s)}return(e.register??lT)(o),{ok:!0,messages:[`Registered ${ts} as the Login Items identity.`]}}catch(c){return{ok:!1,messages:[`Could not write the Login Items identity app (${c?.name??"Error"}). Background items will show the signing certificate name instead.`]}}},yw=async(e={})=>{if((e.os??uw())!=="darwin")return{ok:!0,messages:[]};if(es())return{ok:!0,messages:[]};let n=fw(e.home??zu());try{return await pw(n,{recursive:!0,force:!0}),{ok:!0,messages:[`Removed ${ts}.app.`]}}catch(r){return{ok:!1,messages:[`Could not remove the Login Items identity app (${r?.name??"Error"}).`]}}};var Ae="0.11.166";var tn=(e=process.env)=>e.CODESPACES==="true"||e.CODESPACE_NAME?"codespaces":e.REMOTE_CONTAINERS==="true"||e.REMOTE_CONTAINERS_IPC||e.DEVCONTAINER?"devcontainer":e.SSH_CONNECTION||e.SSH_TTY?"ssh":null,$r=(e=process.env)=>tn(e)!==null,kn=e=>e==="codespaces"||e==="devcontainer",Oo=e=>{switch(e){case"codespaces":return"a GitHub Codespace";case"devcontainer":return"a dev container";case"ssh":return"an SSH session"}};var gw=()=>Br(wc(),".anyray","logs"),ep=()=>Br(gw(),"connect-refresh.log"),gc=1800,bc="ai.anyray.connect.refresh",os="anyray-refresh",np="\\AnyrayConnectKeyRefresh",Ac=bc,as=os,rs=np,fT=String(gc/60),rp=(e,t)=>{let n=pT(e,t,{stdio:"ignore"});return{ok:!n.error&&n.status===0}},op=()=>Br(wc(),"Library","LaunchAgents",`${Ac}.plist`),ap=()=>Br(wc(),".config","systemd","user"),ww=()=>Br(ap(),`${as}.service`),yc=()=>Br(ap(),`${as}.timer`),mc=e=>e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),Cc=()=>zn(jn),hT=(e=Cc())=>{let{command:t,args:n}=e,r=[t,...n].map(o=>` <string>${mc(o)}</string>`).join(`
26
26
  `);return`<?xml version="1.0" encoding="UTF-8"?>
27
27
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
28
28
  <plist version="1.0">
@@ -43,18 +43,40 @@ public struct SecurityDeviceIdentityStore {
43
43
  "ai.anyray.connect.device-identity.v1".utf8
44
44
  )
45
45
  public static let label = "Anyray Connect Device Identity v1"
46
+ // Not keychain-access-groups: that gets a Developer ID CLI AMFI-killed at launch.
47
+ public static let accessGroupEntitlement = "com.apple.security.application-groups"
46
48
 
47
49
  private let applicationTag: Data
48
50
  private let label: String
51
+ private let accessGroup: String?
49
52
 
50
53
  public init() {
51
- applicationTag = Self.applicationTag
52
- label = Self.label
54
+ self.init(
55
+ applicationTag: Self.applicationTag,
56
+ label: Self.label,
57
+ accessGroup: Self.entitledAccessGroup()
58
+ )
53
59
  }
54
60
 
55
- init(applicationTag: Data, label: String) {
61
+ init(applicationTag: Data, label: String, accessGroup: String?) {
56
62
  self.applicationTag = applicationTag
57
63
  self.label = label
64
+ self.accessGroup = accessGroup
65
+ }
66
+
67
+ public static func entitledAccessGroup() -> String? {
68
+ guard let task = SecTaskCreateFromSelf(nil),
69
+ let groups = SecTaskCopyValueForEntitlement(
70
+ task,
71
+ accessGroupEntitlement as CFString,
72
+ nil
73
+ ) as? [String],
74
+ let first = groups.first,
75
+ !first.isEmpty
76
+ else {
77
+ return nil
78
+ }
79
+ return first
58
80
  }
59
81
 
60
82
  public func loadOrCreate() throws -> SecurityDeviceIdentityKey {
@@ -103,7 +125,7 @@ public struct SecurityDeviceIdentityStore {
103
125
  private func findPrivateKey() throws -> SecKey? {
104
126
  let authenticationContext = LAContext()
105
127
  authenticationContext.interactionNotAllowed = true
106
- let query: [CFString: Any] = [
128
+ var query: [CFString: Any] = [
107
129
  kSecClass: kSecClassKey,
108
130
  kSecAttrApplicationTag: applicationTag,
109
131
  kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
@@ -114,6 +136,7 @@ public struct SecurityDeviceIdentityStore {
114
136
  kSecUseDataProtectionKeychain: true,
115
137
  kSecUseAuthenticationContext: authenticationContext,
116
138
  ]
139
+ if let accessGroup { query[kSecAttrAccessGroup] = accessGroup }
117
140
  var result: CFTypeRef?
118
141
  let status = SecItemCopyMatching(query as CFDictionary, &result)
119
142
  if status == errSecItemNotFound { return nil }
@@ -127,7 +150,7 @@ public struct SecurityDeviceIdentityStore {
127
150
  }
128
151
 
129
152
  private func createPrivateKey(accessible: CFString) throws -> SecKey {
130
- let privateAttributes: [CFString: Any] = [
153
+ var privateAttributes: [CFString: Any] = [
131
154
  kSecAttrIsPermanent: true,
132
155
  kSecAttrApplicationTag: applicationTag,
133
156
  kSecAttrLabel: label,
@@ -135,6 +158,7 @@ public struct SecurityDeviceIdentityStore {
135
158
  kSecAttrAccessible: accessible,
136
159
  kSecAttrSynchronizable: false,
137
160
  ]
161
+ if let accessGroup { privateAttributes[kSecAttrAccessGroup] = accessGroup }
138
162
  let attributes: [CFString: Any] = [
139
163
  kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
140
164
  kSecAttrKeySizeInBits: 256,
@@ -9,11 +9,9 @@ import Testing
9
9
  struct SecurityDeviceIdentityTests {
10
10
  @Test
11
11
  func permanentKeySignsReloadsAndCannotBeExported() throws {
12
- // SwiftPM's ad-hoc test host has no Keychain access group. The signed
13
- // release helper does; run the real integration assertions whenever the
14
- // host carries that entitlement, instead of weakening production to the
15
- // legacy Keychain (which ignores kSecAttrIsExtractable=false).
16
- guard hasKeychainAccessGroup() else { return }
12
+ // Only a signed host has an app group; never fall back to the legacy keychain.
13
+ guard let accessGroup = SecurityDeviceIdentityStore.entitledAccessGroup()
14
+ else { return }
17
15
 
18
16
  let suffix = UUID().uuidString.lowercased()
19
17
  let tag = Data("ai.anyray.connect.device-identity.test.\(suffix)".utf8)
@@ -22,7 +20,8 @@ struct SecurityDeviceIdentityTests {
22
20
 
23
21
  let store = SecurityDeviceIdentityStore(
24
22
  applicationTag: tag,
25
- label: label
23
+ label: label,
24
+ accessGroup: accessGroup
26
25
  )
27
26
  let first = try store.loadOrCreate()
28
27
  let message = Data("{\"issuedAt\":\"fresh\",\"nonce\":\"test\"}".utf8)
@@ -45,6 +44,12 @@ struct SecurityDeviceIdentityTests {
45
44
  )
46
45
  }
47
46
 
47
+ @Test
48
+ func unsignedHostHasNoEntitledAccessGroup() {
49
+ guard !hostIsSignedWithAppGroup() else { return }
50
+ #expect(SecurityDeviceIdentityStore.entitledAccessGroup() == nil)
51
+ }
52
+
48
53
  private func keyQuery(tag: Data) -> [CFString: Any] {
49
54
  [
50
55
  kSecClass: kSecClassKey,
@@ -76,11 +81,11 @@ struct SecurityDeviceIdentityTests {
76
81
  _ = SecItemDelete(keyQuery(tag: tag) as CFDictionary)
77
82
  }
78
83
 
79
- private func hasKeychainAccessGroup() -> Bool {
84
+ private func hostIsSignedWithAppGroup() -> Bool {
80
85
  guard let task = SecTaskCreateFromSelf(nil),
81
86
  let groups = SecTaskCopyValueForEntitlement(
82
87
  task,
83
- "keychain-access-groups" as CFString,
88
+ SecurityDeviceIdentityStore.accessGroupEntitlement as CFString,
84
89
  nil
85
90
  ) as? [String]
86
91
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anyray-connect",
3
- "version": "0.11.165",
3
+ "version": "0.11.166",
4
4
  "description": "Anyray connect \u2014 points local coding tools (Claude Code, Cursor, Windsurf, SDKs) at the Anyray gateway through supported configuration and enrolled personal authentication.",
5
5
  "type": "module",
6
6
  "bin": {