chore(go): audit shared plugin helpers
Drop unused expandHome and standardizeJSONC, share the runtime base URL validator between the guard and audit templates, and trim comments. Claude-Session: https://claude.ai/code/session_01Agoc9bRVE9KL7mmWtey9Vq
This commit is contained in:
1 parent
5e08ab20f5
commit
b085947547
9 files changed
+42
-102
No files matched your search
@@ -344,3 +344,12 @@ func requireNoDroidStagingFiles(t *testing.T, root string) {
|
|||||||
t.Fatalf("walk staging files: %v", err)
|
t.Fatalf("walk staging files: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func standardizeJSONC(source []byte, label string, allowTrailingCommas bool) ([]byte, error) {
|
||||||
|
value, err := parseJSONC(source, label, allowTrailingCommas)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
value.Standardize()
|
||||||
|
return value.Pack(), nil
|
||||||
|
}
|
||||||
@@ -146,30 +146,8 @@ function writeStatusCache(status) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveBaseURL(config) {
|
|
||||||
const value = config.base_url || 'https://api.elydora.com';
|
|
||||||
if (typeof value !== 'string' || /[\\\u0000-\u0020]/.test(value)) {
|
|
||||||
throw new Error('Agent config base_url must be an absolute HTTP or HTTPS URL');
|
|
||||||
}
|
|
||||||
let parsed;
|
|
||||||
try {
|
|
||||||
parsed = new URL(value);
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('Agent config base_url must be an absolute HTTP or HTTPS URL');
|
|
||||||
}
|
|
||||||
if (!['http:', 'https:'].includes(parsed.protocol) || !parsed.hostname) {
|
|
||||||
throw new Error('Agent config base_url must be an absolute HTTP or HTTPS URL');
|
|
||||||
}
|
|
||||||
if (parsed.username || parsed.password || parsed.search || parsed.hash) {
|
|
||||||
throw new Error(
|
|
||||||
'Agent config base_url must exclude credentials, query parameters, and fragments',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return value.replace(/\/+$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolveRemoteStatus(config) {
|
async function resolveRemoteStatus(config) {
|
||||||
const baseURL = resolveBaseURL(config);
|
const baseURL = resolveConfigBaseURL(config);
|
||||||
const token = config.token || '';
|
const token = config.token || '';
|
||||||
if (typeof token !== 'string') throw new Error('Agent config token must be a string');
|
if (typeof token !== 'string') throw new Error('Agent config token must be a string');
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ func buildHookScriptWithOutput(
|
|||||||
return fmt.Sprintf(`#!/usr/bin/env node
|
return fmt.Sprintf(`#!/usr/bin/env node
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// Elydora audit hook for %s. Generated by Elydora SDK.
|
||||||
// Self-contained Elydora audit hook for: %s
|
|
||||||
// Generated by @elydora/sdk — DO NOT EDIT
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
const crypto = require('node:crypto');
|
const crypto = require('node:crypto');
|
||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
@@ -53,10 +50,6 @@ function parsePrivateKey(raw) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Embedded crypto (from @elydora/sdk src/crypto.ts + src/utils.ts)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function base64urlEncode(data) {
|
function base64urlEncode(data) {
|
||||||
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
||||||
return buf.toString('base64url');
|
return buf.toString('base64url');
|
||||||
@@ -158,10 +151,6 @@ function generateNonce() {
|
|||||||
return base64urlEncode(crypto.randomBytes(16));
|
return base64urlEncode(crypto.randomBytes(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Chain state persistence (atomic writes)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function readChainState() {
|
function readChainState() {
|
||||||
let raw;
|
let raw;
|
||||||
try {
|
try {
|
||||||
@@ -265,10 +254,6 @@ function advanceChainState(fromHash, toHash) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Error logging
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function logError(err) {
|
function logError(err) {
|
||||||
try {
|
try {
|
||||||
const ts = new Date().toISOString();
|
const ts = new Date().toISOString();
|
||||||
@@ -281,32 +266,6 @@ function logError(err) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveAuditBaseURL(config) {
|
|
||||||
const value = config.base_url || 'https://api.elydora.com';
|
|
||||||
if (typeof value !== 'string' || /[\\\u0000-\u0020]/.test(value)) {
|
|
||||||
throw new Error('Agent config base_url must be an absolute HTTP or HTTPS URL');
|
|
||||||
}
|
|
||||||
let parsed;
|
|
||||||
try {
|
|
||||||
parsed = new URL(value);
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('Agent config base_url must be an absolute HTTP or HTTPS URL');
|
|
||||||
}
|
|
||||||
if (!['http:', 'https:'].includes(parsed.protocol) || !parsed.hostname) {
|
|
||||||
throw new Error('Agent config base_url must be an absolute HTTP or HTTPS URL');
|
|
||||||
}
|
|
||||||
if (parsed.username || parsed.password || parsed.search || parsed.hash) {
|
|
||||||
throw new Error(
|
|
||||||
'Agent config base_url must exclude credentials, query parameters, and fragments',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return value.replace(/\/+$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Main hook logic
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
async function postOperation(baseUrl, token, eor, deadline) {
|
async function postOperation(baseUrl, token, eor, deadline) {
|
||||||
const remaining = deadline - performance.now();
|
const remaining = deadline - performance.now();
|
||||||
if (remaining <= 0) throw new Error('Audit API retry budget exhausted');
|
if (remaining <= 0) throw new Error('Audit API retry budget exhausted');
|
||||||
@@ -344,7 +303,6 @@ async function postOperation(baseUrl, token, eor, deadline) {
|
|||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
try {
|
try {
|
||||||
// Read JSON from stdin
|
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
for await (const chunk of process.stdin) {
|
for await (const chunk of process.stdin) {
|
||||||
chunks.push(chunk);
|
chunks.push(chunk);
|
||||||
@@ -380,7 +338,6 @@ async function main() {
|
|||||||
hookData.conversation_id || hookData.session_id || hookData.sessionId ||
|
hookData.conversation_id || hookData.session_id || hookData.sessionId ||
|
||||||
hookData.session || hookData.taskId || 'unknown';
|
hookData.session || hookData.taskId || 'unknown';
|
||||||
|
|
||||||
// Read agent config and private key
|
|
||||||
let config;
|
let config;
|
||||||
let privateKey;
|
let privateKey;
|
||||||
try {
|
try {
|
||||||
@@ -408,7 +365,7 @@ async function main() {
|
|||||||
const orgId = config.org_id;
|
const orgId = config.org_id;
|
||||||
const agentId = config.agent_id;
|
const agentId = config.agent_id;
|
||||||
const kid = config.kid;
|
const kid = config.kid;
|
||||||
const baseUrl = resolveAuditBaseURL(config);
|
const baseUrl = resolveConfigBaseURL(config);
|
||||||
const token = config.token || '';
|
const token = config.token || '';
|
||||||
if (typeof orgId !== 'string' || !orgId || typeof agentId !== 'string' ||
|
if (typeof orgId !== 'string' || !orgId || typeof agentId !== 'string' ||
|
||||||
!agentId || typeof kid !== 'string' || !kid) {
|
!agentId || typeof kid !== 'string' || !kid) {
|
||||||
@@ -416,7 +373,7 @@ async function main() {
|
|||||||
}
|
}
|
||||||
if (typeof token !== 'string') throw new Error('Agent config token must be a string');
|
if (typeof token !== 'string') throw new Error('Agent config token must be a string');
|
||||||
|
|
||||||
// Build and submit, retrying from the server chain head on PREV_HASH_MISMATCH
|
// Retry from the server chain head on PREV_HASH_MISMATCH.
|
||||||
const payload = NATIVE_PAYLOAD ? hookData : {
|
const payload = NATIVE_PAYLOAD ? hookData : {
|
||||||
tool_name: toolName,
|
tool_name: toolName,
|
||||||
tool_input: toolInput,
|
tool_input: toolInput,
|
||||||
|
|||||||
@@ -17,15 +17,6 @@ type jsoncEditor struct {
|
|||||||
label string
|
label string
|
||||||
}
|
}
|
||||||
|
|
||||||
func standardizeJSONC(source []byte, label string, allowTrailingCommas bool) ([]byte, error) {
|
|
||||||
value, err := parseJSONC(source, label, allowTrailingCommas)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
value.Standardize()
|
|
||||||
return value.Pack(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseJSONC(source []byte, label string, allowTrailingCommas bool) (hujson.Value, error) {
|
func parseJSONC(source []byte, label string, allowTrailingCommas bool) (hujson.Value, error) {
|
||||||
value, err := hujson.Parse(source)
|
value, err := hujson.Parse(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,22 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// expandHome replaces a leading ~ with the user's home directory.
|
// readJSONFile reads a JSON object; a missing file yields an empty map.
|
||||||
func expandHome(p string) (string, error) {
|
|
||||||
if !strings.HasPrefix(p, "~") {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("resolve home directory: %w", err)
|
|
||||||
}
|
|
||||||
return filepath.Join(home, p[1:]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readJSONFile reads a JSON file into a map. Returns an empty map if the file does not exist.
|
|
||||||
func readJSONFile(path string) (map[string]interface{}, error) {
|
func readJSONFile(path string) (map[string]interface{}, error) {
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -53,7 +40,7 @@ func writeJSONFile(path string, data map[string]interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hookScriptPath returns the default path for the hook script inside ~/.elydora/<agentId>/.
|
// hookScriptPath returns ~/.elydora/<agentId>/hook.js.
|
||||||
func hookScriptPath(agentId string) (string, error) {
|
func hookScriptPath(agentId string) (string, error) {
|
||||||
agentDirectory, err := ResolveAgentRuntimeDirectory(agentId)
|
agentDirectory, err := ResolveAgentRuntimeDirectory(agentId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -62,7 +49,7 @@ func hookScriptPath(agentId string) (string, error) {
|
|||||||
return filepath.Join(agentDirectory, "hook.js"), nil
|
return filepath.Join(agentDirectory, "hook.js"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// guardScriptPath returns the default path for the guard script inside ~/.elydora/<agentId>/.
|
// guardScriptPath returns ~/.elydora/<agentId>/guard.js.
|
||||||
func guardScriptPath(agentId string) (string, error) {
|
func guardScriptPath(agentId string) (string, error) {
|
||||||
agentDirectory, err := ResolveAgentRuntimeDirectory(agentId)
|
agentDirectory, err := ResolveAgentRuntimeDirectory(agentId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ package plugins
|
|||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
// extractElydoraScriptPath supports adapters that still use the legacy shared
|
// extractElydoraScriptPath reads the legacy shared command-hook shape.
|
||||||
// command-hook shape. Provider-specific adapters should define exact ownership.
|
|
||||||
func extractElydoraScriptPath(hookArray any) string {
|
func extractElydoraScriptPath(hookArray any) string {
|
||||||
entries, _ := hookArray.([]any)
|
entries, _ := hookArray.([]any)
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ var SupportedAgents = map[string]AgentRegistryEntry{
|
|||||||
"qwen": {Name: "Qwen Code", ConfigDir: "~/.qwen", ConfigFile: "settings.json"},
|
"qwen": {Name: "Qwen Code", ConfigDir: "~/.qwen", ConfigFile: "settings.json"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPlugin creates a plugin instance for the given agent name.
|
// NewPlugin returns the plugin for an agent name, or nil when unsupported.
|
||||||
// Returns nil if the agent is not supported.
|
|
||||||
func NewPlugin(agentName string) AgentPlugin {
|
func NewPlugin(agentName string) AgentPlugin {
|
||||||
switch agentName {
|
switch agentName {
|
||||||
case "augment":
|
case "augment":
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ type agentRuntimeConfig struct {
|
|||||||
AgentName string `json:"agent_name"`
|
AgentName string `json:"agent_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteRuntimeFileAtomic replaces one managed runtime file through a flushed,
|
// WriteRuntimeFileAtomic replaces one managed runtime file atomically.
|
||||||
// same-directory temporary file.
|
|
||||||
func WriteRuntimeFileAtomic(
|
func WriteRuntimeFileAtomic(
|
||||||
path string,
|
path string,
|
||||||
label string,
|
label string,
|
||||||
@@ -40,8 +39,7 @@ func WriteRuntimeFileAtomic(
|
|||||||
return writeChanges([]*fileChange{change}, "write "+label, nil)
|
return writeChanges([]*fileChange{change}, "write "+label, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateHookScript atomically commits the agent config, private key, and
|
// GenerateHookScript commits the config, private key, and audit runtime together.
|
||||||
// self-contained Node.js audit runtime.
|
|
||||||
func GenerateHookScript(destPath string, config InstallConfig) error {
|
func GenerateHookScript(destPath string, config InstallConfig) error {
|
||||||
return generateHookScriptWithRename(destPath, config, nil)
|
return generateHookScriptWithRename(destPath, config, nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,4 +142,26 @@ function writeProtectedJson(filePath, label, value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveConfigBaseURL(config) {
|
||||||
|
const value = config.base_url || 'https://api.elydora.com';
|
||||||
|
if (typeof value !== 'string' || /[\\\u0000-\u0020]/.test(value)) {
|
||||||
|
throw new Error('Agent config base_url must be an absolute HTTP or HTTPS URL');
|
||||||
|
}
|
||||||
|
let parsed;
|
||||||
|
try {
|
||||||
|
parsed = new URL(value);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Agent config base_url must be an absolute HTTP or HTTPS URL');
|
||||||
|
}
|
||||||
|
if (!['http:', 'https:'].includes(parsed.protocol) || !parsed.hostname) {
|
||||||
|
throw new Error('Agent config base_url must be an absolute HTTP or HTTPS URL');
|
||||||
|
}
|
||||||
|
if (parsed.username || parsed.password || parsed.search || parsed.hash) {
|
||||||
|
throw new Error(
|
||||||
|
'Agent config base_url must exclude credentials, query parameters, and fragments',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value.replace(/\/+$/, '');
|
||||||
|
}
|
||||||
`
|
`
|
||||||
Reference in new issue
Block a user