mirror of
https://github.com/GMGNAI/gmgn-skills.git
synced 2026-08-22 20:48:05 +00:00
fix(security): defend against prompt injection via token metadata
Address HackenProof report GMGNWM-143, where attacker-controlled token metadata could hijack an AI agent driving gmgn-cli into executing an unauthorized trade. Move guardrails from overridable SKILL.md text into code. - Add src/sanitize.ts: neutralize prompt-injection framing and hidden/control characters in API output (via printResult) and validate create-token metadata - Add src/confirm.ts: code-enforced human confirmation for financial writes (swap, multi-swap, order strategy create, cooking create) — reads a typed "yes" from /dev/tty; automation requires GMGN_ALLOW_AUTOMATED_TRADES=1 + --yes - Harden config.ts: tighten ~/.config/gmgn/.env to 0600 and warn if world-readable - Update SKILL.md files, Readme.md and Readme.zh.md to document the gate, the --yes flag, and untrusted-metadata handling Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b18d7deb32
commit
fdc7a3fb16
+25
-7
@@ -2,6 +2,8 @@ import { Command } from "commander";
|
||||
import { OpenApiClient, CreateTokenParams } from "../client/OpenApiClient.js";
|
||||
import { getConfig } from "../config.js";
|
||||
import { exitOnError, printResult } from "../output.js";
|
||||
import { confirmTrade } from "../confirm.js";
|
||||
import { sanitizeMetadataField, validateMetadataUrl, MAX_DESCRIPTION_LEN, MAX_NAME_LEN } from "../sanitize.js";
|
||||
import { validateChain } from "../validate.js";
|
||||
|
||||
export function registerCookingCommands(program: Command): void {
|
||||
@@ -70,6 +72,7 @@ export function registerCookingCommands(program: Command): void {
|
||||
.option("--buy-trade-config <json>", "Buy-side trade config for CondMarket orders as JSON (TradeParam)")
|
||||
.option("--sell-trade-config <json>", "Sell-side trade config for auto-sell / pending_sell as JSON (TradeParam)")
|
||||
.option("--sell-configs <json>", "Auto-sell strategy list as JSON array (CookingSellConfig[])")
|
||||
.option("--yes", "Skip the interactive confirmation prompt (requires GMGN_ALLOW_AUTOMATED_TRADES=1)")
|
||||
.option("--raw", "Output raw JSON")
|
||||
.action(async (opts) => {
|
||||
if (!opts.image && !opts.imageUrl) {
|
||||
@@ -85,20 +88,23 @@ export function registerCookingCommands(program: Command): void {
|
||||
console.error(`[gmgn-cli] cooking create does not support robinhood, got "${opts.chain}"`);
|
||||
process.exit(1);
|
||||
}
|
||||
// Validate/clean all free-text and link metadata before publishing. This
|
||||
// prevents the CLI from being used to mint tokens whose metadata carries a
|
||||
// prompt-injection payload aimed at other users' AI agents.
|
||||
const params: CreateTokenParams = {
|
||||
chain: opts.chain,
|
||||
dex: opts.dex,
|
||||
from_address: opts.from,
|
||||
name: opts.name,
|
||||
symbol: opts.symbol,
|
||||
name: sanitizeMetadataField(opts.name, "--name", MAX_NAME_LEN),
|
||||
symbol: sanitizeMetadataField(opts.symbol, "--symbol", MAX_NAME_LEN),
|
||||
buy_amt: opts.buyAmt,
|
||||
};
|
||||
if (opts.image) params.image = opts.image;
|
||||
if (opts.imageUrl) params.image_url = opts.imageUrl;
|
||||
if (opts.description) params.description = opts.description;
|
||||
if (opts.website) params.website = opts.website;
|
||||
if (opts.twitter) params.twitter = opts.twitter;
|
||||
if (opts.telegram) params.telegram = opts.telegram;
|
||||
if (opts.imageUrl) params.image_url = validateMetadataUrl(opts.imageUrl, "--image-url");
|
||||
if (opts.description) params.description = sanitizeMetadataField(opts.description, "--description", MAX_DESCRIPTION_LEN);
|
||||
if (opts.website) params.website = validateMetadataUrl(opts.website, "--website");
|
||||
if (opts.twitter) params.twitter = validateMetadataUrl(opts.twitter, "--twitter");
|
||||
if (opts.telegram) params.telegram = validateMetadataUrl(opts.telegram, "--telegram");
|
||||
if (opts.slippage != null) params.slippage = opts.slippage;
|
||||
if (opts.autoSlippage) params.auto_slippage = true;
|
||||
if (opts.fee) params.fee = opts.fee;
|
||||
@@ -130,6 +136,18 @@ export function registerCookingCommands(program: Command): void {
|
||||
if (opts.buyTradeConfig) params.buy_trade_config = JSON.parse(opts.buyTradeConfig);
|
||||
if (opts.sellTradeConfig) params.sell_trade_config = JSON.parse(opts.sellTradeConfig);
|
||||
if (opts.sellConfigs) params.sell_configs = JSON.parse(opts.sellConfigs);
|
||||
confirmTrade({
|
||||
action: "Create token",
|
||||
lines: [
|
||||
`Chain: ${params.chain}`,
|
||||
`Launchpad: ${params.dex}`,
|
||||
`Wallet: ${params.from_address}`,
|
||||
`Name: ${params.name}`,
|
||||
`Symbol: ${params.symbol}`,
|
||||
`Buy amount: ${params.buy_amt}`,
|
||||
],
|
||||
}, opts.yes);
|
||||
|
||||
const client = new OpenApiClient(getConfig(true));
|
||||
const data = await client.createToken(params).catch(exitOnError);
|
||||
printResult(data, opts.raw);
|
||||
|
||||
Reference in New Issue
Block a user