Back to skill

Security audit

Gdpr Checker

Security checks for vulnerabilities and agentic risk

Overview

This GDPR audit skill is mostly purpose-aligned, but it can alter a real ledger record and write a full plaintext financial export without strong safeguards.

Use this only after reviewing the commands, preferably on a non-production checkout or database copy. Do not run the Decision#update audit against valuable live data unless it is wrapped in rollback-safe test logic, and send exports only to a user-approved private directory with restrictive permissions or encryption, then delete them when no longer needed.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:101
Finding

Immutability Audit May Permanently Tamper with the Latest Ledger Record

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 101-109
Vulnerability Type: Unsafe mutation of live application data
Risk Level: High

Complete Code Snippet:

ruby
# Check if update is blocked
echo "--- Decision#update ---"
bin/rails runner "
  begin
    d = Decision.last
    d.update(action: 'TAMPERED')
    puts '❌ GDPR CONFLICT: update allowed on Decision'
  rescue => e
    puts '✅ Update blocked (immutable): ' + e.message[0..80]
  end
" 2>&1

Technical Analysis

The audit tests ledger immutability by selecting Decision.last and attempting to change its action to TAMPERED. This is a write against an existing persisted record, not a disposable test fixture.

If the immutability control is missing or defective—the exact condition this test is intended to detect—d.update(...) can succeed and commit the modification. The command does not use a transaction that is unconditionally rolled back, restore the original value, verify that the Rails environment is non-production, or request confirmation before writing. Consequently, executing a security audit can itself corrupt the data under audit.

The code also does not verify that Decision.last returned a record. An empty table results in a NoMethodError, which the broad rescue clause incorrectly reports as evidence that updates are blocked. This creates a false-positive audit result.

Attack Path

  1. A user or Agent follows the documented right-to-delete audit against a running ZK-Bankir checkout.
  2. The Rails runner connects to the application's configured database.
  3. Decision.last selects the latest real ledger entry.
  4. If update protection is absent or incomplete, d.update(action: 'TAMPERED') commits successfully.
  5. The latest financial decision remains altered after the audit finishes.
  6. Any audit trail or hash chain derived from that record may become inconsistent or untrustworthy.

Impact As

...[truncated 539 chars]

Remediation
View remediation

Remediation Suggestions

  • Never test immutability by modifying an existing record.
  • Restrict destructive verification to a dedicated test environment and abort when Rails.env.production? is true.
  • Create a disposable fixture specifically for the test rather than selecting Decision.last.
  • Wrap the entire test in a transaction and guarantee rollback with ensure.
  • Prefer non-mutating verification, such as inspecting model callbacks, database permissions, triggers, and constraints.
  • Use update! when testing so validation failures are not confused with successful protection.
  • Explicitly handle an empty table instead of treating every exception as proof of immutability.
  • Require explicit user confirmation before any audit operation that writes to a database.

A safer pattern is:

ruby
abort "Refusing to run in production" if Rails.env.production?

Decision.transaction do
  test_record = Decision.create!(action: "TEST", amount: 0)
  begin
    test_record.update!(action: "TAMPERED")
    warn "Immutability control failed"
  rescue ActiveRecord::ReadOnlyRecord, ActiveRecord::RecordNotSaved => e
    puts "Update correctly blocked: #{e.message}"
  ensure
    raise ActiveRecord::Rollback
  end
end

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:125
Finding

Full Financial Dataset Is Exported to a Plaintext File with Default Permissions

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 125-161
Vulnerability Type: Plaintext storage of sensitive financial data
Risk Level: Medium

Complete Code Snippet:

bash
cd ZK_BANKIR_PATH

echo "=== Data Export ==="

EXPORT_FILE="gdpr-export-$(date +%Y%m%d-%H%M%S).json"

bin/rails runner "
  require 'json'
  
  export = {
    exported_at: Time.now.iso8601,
    system: 'ZK-Bankir',
    decisions: Decision.all.map { |d|
      {
        id: d.id,
        action: d.action,
        amount: d.amount.to_s,
        status: d.status,
        created_at: d.created_at.iso8601,
        hash: d.hash_chain
      }
    },
    treasury: Treasury.all.map { |t|
      {
        asset: t.asset,
        balance: t.balance.to_s,
        updated_at: t.updated_at.iso8601
      }
    }
  }
  
  File.write('#{EXPORT_FILE}', JSON.pretty_generate(export))
  puts \"✅ Export written to #{EXPORT_FILE} (#{export[:decisions].count} decisions)\"
" 2>&1

echo "Export file: $(pwd)/$EXPORT_FILE"

Technical Analysis

The portability workflow reads all Decision and Treasury records and stores them as unencrypted JSON in the current project directory. The export includes financial actions, amounts, statuses, timestamps, ledger hashes, assets, and balances.

File.write creates the file according to the process umask rather than enforcing a restrictive mode. On systems with a permissive umask, other local accounts may be able to read it. Writing to the current directory also increases the likelihood that the export will be committed to version control, included in backups, indexed, synchronized to cloud storage, or left behind indefinitely.

Although the Skill documents GDPR_EXPORT_DIR, this workflow does not use it. It also has no destination validation, encryption option, redaction controls, retention period, secure cleanup process, or explicit warning before exporting the ...[truncated 1094 chars]

Remediation
View remediation

Remediation Suggestions

  • Require explicit confirmation before exporting the complete financial dataset.
  • Use GDPR_EXPORT_DIR rather than the project working directory.
  • Create the export directory with mode 0700 and each export file with mode 0600.
  • Refuse destinations inside source-control worktrees unless explicitly overridden.
  • Support authenticated encryption for exports, with keys or passphrases obtained through a secure input mechanism.
  • Permit users to select date ranges and fields so only necessary data is exported.
  • Define a retention period and provide a secure cleanup workflow.
  • Exclude the export directory through .gitignore and relevant backup or synchronization settings.
  • Avoid loading the complete database into memory; stream records for large datasets.
  • Clearly display the destination, sensitivity warning, and resulting permissions.

A restrictive file-creation pattern is:

ruby
require "fileutils"
require "json"

export_dir = ENV.fetch("GDPR_EXPORT_DIR", "./exports")
FileUtils.mkdir_p(export_dir, mode: 0o700)
File.chmod(0o700, export_dir)

path = File.join(export_dir, "gdpr-export-#{Time.now.strftime('%Y%m%d-%H%M%S')}.json")
File.open(path, File::WRONLY | File::CREAT | File::EXCL, 0o600) do |file|
  file.write(JSON.pretty_generate(export))
end
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (4)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 72)May include surrounding context.

md
# Check credentials setup
echo "--- Credentials ---"
test -f config/credentials.yml.enc && echo "✅ Encrypted credentials exist" || echo "❌ MISSING"
test -f config/master.key 2>/dev/null && echo "⚠️  Master key present (should be gitignored)" || echo "✅ No master.key in repo"

# Check database encryption

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill is presented as an audit/checker, but this section performs a live export of all user data and writes it to a JSON file on disk. That creates a new plaintext copy of sensitive financial records, expanding exposure through local compromise, backups, accidental sharing, or insecure permissions.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The documentation says to validate GDPR portability procedures, but the embedded command actually executes a full data export and persists it locally. This mismatch is dangerous because operators may run what they believe is a passive compliance check while unintentionally creating a sensitive artifact containing complete decision and treasury data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The command writes a machine-readable export of user data to disk without any explicit warning about the sensitivity of the output file. Even in a local-only context, unencrypted JSON exports are easy to index, sync, back up, or exfiltrate, so users need clear notice before creating them.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.