datasets: define the agent-trace schema and seed the first entries - #62
Conversation
aece5f8 to
b14e220
Compare
|
Revised the description and force-pushed a corrected commit before anyone had to read the first one. Self-review turned up three things: |
emmanuelgjr
left a comment
There was a problem hiding this comment.
Thank you for this work. The schema design is thoughtful, and validate.py is solid: it has no network or subprocess calls and scans the whole entry for secrets. We verified the corpus figures you cite (123/25, 992/2,503, 62.4%) against the Zenodo record's aggregates.json, and they check out. Two things block merging.
1. Vendor-neutrality policy. This is a global, vendor-neutral OWASP repository. Merged files credit contributors by name only. Contributor-owned sources such as your own corpus, product or website can't serve as evidence unless they are independently established. Zenodo record 10.5281/zenodo.21449149 ("mcpindex Drift Report") lists you as its sole creator, and the entries also link to https://mcpindex.ai/drift-report and https://mcpindex.ai/methodology. The PR description calls it "a public CC BY 4.0 contract-drift corpus" without saying it's yours.
Requested changes:
- Remove the
mcpindex.ailinks from all entries and the example. - Remove the self-authored Zenodo corpus as
provenance.evidence. Instead, back each precondition with independent public sources (for example the MCP specification'sdestructiveHintsemantics, published advisories, or third-party research). If you can't, mark the traceshypothetical. - Keep
contributor.nameas your name; that's fine.
2. Schema direction needs a maintainer decision first. This PR and #67 both define the format for agentdataflow_toolexchange_traces/. They conflict in the README and take opposite positions on the central data_validation/schemas/agentdataflow_trace.schema.json. Maintainers will decide between dataset-local and central schemas (your options 1–3) before either PR merges. When you update, please split the PR: first the schema, validator and README, then the entries in a follow-up. That keeps review of the format decision separate from review of the data.
Thanks again, and happy to re-review once updated.
agentdataflow_toolexchange_traces/ has been open for contributions with a TODO where its data format should be. This defines the format and ships the tooling that enforces it. Entries follow in a separate PR, so the format can be reviewed without the data riding along. provenance.tier records how a trace was obtained - sanitized production, lab or CTF, derived from a documented public fact, or hypothetical - because the people who hold real agent traces mostly cannot publish them. Every tier above hypothetical requires evidence a reader can check without the contributor, enforced by validate.py, so the kinds of contribution never pretend to be one another. A contributor-controlled source does not establish a precondition at any tier; that rule cannot be checked mechanically and is stated as a review-time obligation in the README and schema, not implied to be automated. The category enum matches the values already used by the repo's central data_validation/schemas/agentdataflow_trace.schema.json. Where this schema diverges from that one, the divergence is deliberate and set out in the pull request description. validate.py checks schema conformance, the span graph (including that a span cannot name itself or a later span as its parent), citation form per evidence type, and ISO dates on date_added/date_observed and every span timestamp in every environment, and scans every string in an entry - not only span payloads - for credential-, secret-, and internal-hostname-shaped values and for clusters of addresses that look like real routable public IPv4 addresses, independently of the contributor's sanitization attestation. It passes with entries/ empty, validating example.json on its own. A GitHub Actions workflow runs validate.py and checks index.csv is current, path-filtered to this dataset, so the checks above run on every PR rather than depending on a contributor having run them locally.
b14e220 to
09ca357
Compare
|
Both points taken, and I went back through my own work harder than the first pass. Updated, and the PR is now split. 1. Vendor neutrality. You are right and I should have disclosed it. The Zenodo record is my own work, and describing it in the PR as a public CC BY 4.0 contract-drift corpus without saying whose it was is the part that should not have happened. The DOI and both mcpindex.ai links are gone from Every precondition is now carried by a source you can check without me. I also caught a real problem with my own first pass at this, past what you flagged: the trust-warning citation dropped the specification's own conditional. It says untrusted unless the server is trusted, and the server in this trace is one the client already bound and trusts, so the annotation isn't forbidden - what's missing is any statement that the trust gets re-checked when the annotation later changes, which is the actual mechanism the trace shows. I rewrote that evidence item to say the narrower, correct thing instead of the broader claim I'd written the first time. Both entries citing the CVE now also say what it does not establish: it needs an attacker with write access, so it backs an adversarial path to the same end-state, not the non-adversarial one the traces model. Every count is gone with the corpus, and I renamed Two format changes came out of this. 2. Split. This PR is now While I was in there I also fixed four things in the validator that had nothing to do with your review but should not have shipped: a span could name itself as its own parent and pass; the scan had no pattern for internal hostnames or credential-shaped On the direction itself I have nothing to add to the three options in the description, and no objection to #67 being the one you take it from. If the answer is the central schema, the entries re-target it and the evidence rules above hold either way. One thing still open from last time: |
emmanuelgjr
left a comment
There was a problem hiding this comment.
Re-reviewed the revised PR end to end. Verified locally: validate.py passes with entries/ empty; a deliberately broken entry (self-parent span, credential-shaped assignment, internal hostname) fails with exit 1 and precise messages; no reference to the removed corpus or its links remains anywhere in the diff; the CI workflow is SHA-pinned, path-filtered, contents: read, plain pull_request. The rewritten evidence is exactly what we asked for — narrow claims against the MCP specification and CVE-2025-54136, each stating what it does and does not establish — and the ownership rule now written into the README says the policy better than we had it written anywhere. Thank you for taking the vendor-neutrality point the way you did.
Answers to your two open questions:
1. Schema direction: option 2. Dataset-local schemas are authoritative — that is the precedent exploit_dataset set, and yours is the design with enforcement behind it. Please go ahead with the follow-up replacing data_validation/schemas/agentdataflow_trace.schema.json with a pointer to the dataset-local schema, and file the issue recording the same drift for exploit_dataset. The entries follow-up (both traces + index.csv) is welcome as soon as this merges; the derived_from_public_documentation tier as you've redefined it is accepted. Keep mitre_atlas_mapping release-pinned — #59/#65 settled that argument in favor of the pin.
2. License. This contribution is accepted under the repository's license, CC BY-SA 4.0 (the LICENSE file is the CC BY-SA summary; GitHub's NOASSERTION is a detection artifact). Should #44 land, code files (validate.py, build_index.py, the workflow) would be relicensed Apache-2.0 — reply here confirming you're fine with that and the record is complete either way.
One optional follow-up: the break-test cases you describe aren't committed. With CI now running the validator, a small tests/ of known-bad fixtures would keep them from rotting.
Merging. #67 will be asked to re-target TRACE-0001 to this format — its adversarial, non-MCP trace is variety this dataset wants.
Maintainer conversion, offered in review. GenAI-Security-Project#62 settled the schema direction after this PR was opened, so the trace moves to entries/DSGAI-TRACE-overbroad-search-context-egress-self-report-gap.json against the dataset-local schema: typed spans (7, with the tool result and relay record as their own spans), per-span data classes and sensitivity, findings on s3 (DSGAI06) and s5 (DSGAI15), the four sensitivity annotations recast as falsifiable security_observations, and concrete mitigations. The README changes are dropped - main's merged README already documents the format. Two semantic changes from the original, both stated in the entry's notes: disposition is unintentional_failure (the central schema offered only benign/adversarial and no adversary appears in the flow), and provenance.tier is hypothetical (a hand-constructed illustration, per the dataset's ownership rule). Content, placeholders, byte counts, and the self-report-divergence thesis are unchanged. validate.py: 1 entry + example.json pass. index.csv ships with this first entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJVKF6raJCNKBWjTkDSqR4
* docs(agentdataflow): document trace format and add the first trace
The dataset README marked the data format as a TODO while
data_validation/schemas/agentdataflow_trace.schema.json already defined
one. Replace the TODO with the schema's required and optional fields, and
add a proposed shape for the objects inside trace_data, which the schema
leaves unconstrained.
Add TRACE-0001, the directory's first trace. It is adversarial: a
tool-call scope pulled an out-of-scope document into the working context
and a later send carried the whole context, and the agent's own account
of the run is accurate about the total and silent about the attachment.
The per-step observed_by key is what makes that divergence readable, so
the example exercises it rather than assuming the agent's record is
reliable.
Validated against the schema with:
python -m jsonschema -i \
datasets/agentdataflow_toolexchange_traces/TRACE-0001.json \
data_validation/schemas/agentdataflow_trace.schema.json
* docs(agentdataflow): move the first trace under entries/
Match the one-entry-per-file layout exploit_dataset uses, as requested in
review, and point the README's validation command at the new path.
Signed-off-by: Sankalp Gilda <sankalp.gilda@gmail.com>
* Re-target TRACE-0001 to the merged dataset-local format
Maintainer conversion, offered in review. #62 settled the schema
direction after this PR was opened, so the trace moves to
entries/DSGAI-TRACE-overbroad-search-context-egress-self-report-gap.json
against the dataset-local schema: typed spans (7, with the tool result
and relay record as their own spans), per-span data classes and
sensitivity, findings on s3 (DSGAI06) and s5 (DSGAI15), the four
sensitivity annotations recast as falsifiable security_observations,
and concrete mitigations. The README changes are dropped - main's
merged README already documents the format.
Two semantic changes from the original, both stated in the entry's
notes: disposition is unintentional_failure (the central schema offered
only benign/adversarial and no adversary appears in the flow), and
provenance.tier is hypothetical (a hand-constructed illustration, per
the dataset's ownership rule). Content, placeholders, byte counts, and
the self-report-divergence thesis are unchanged.
validate.py: 1 entry + example.json pass. index.csv ships with this
first entry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KJVKF6raJCNKBWjTkDSqR4
---------
Signed-off-by: Sankalp Gilda <sankalp.gilda@gmail.com>
Co-authored-by: emmanuelgjr <emmanuelgjr@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…README.md for the OWASP GenAI Data Security Initiative contribution: PR #62 (GenAI-Security-Project/GenAI-Data-Security-Initiative#62), merged 2026-09-15, defining the agent tool-exchange trace dataset's schema and validation tooling. This is a SEPARATE credit from the existing 'OWASP GenAI Data Security Best Practices v2' row - a different sub-project, a different repository, do not merge them into one row or edit the existing row. Insert the new row immediately after the existing 'OWASP GenAI Data Security Best Practices v2' row (same table, same column widths, same markdown table syntax), matching this exact shape and badge convention (Contributor badge, green #10b981, style=for-the-badge, matching the FIASSE and Best Practices v2 rows): | **OWASP GenAI Data Security Initiative** | [](https://github.com/GenAI-Security-Project/GenAI-Data-Security-Initiative) | Defined the data schema and validation tooling for the agent tool-exchange trace dataset ([PR #62](GenAI-Security-Project/GenAI-Data-Security-Initiative#62)): a JSON Schema, a validator covering the span graph, provenance and citation form, and a CI workflow that runs it on every change | Do not touch any other row, section, or file. Do not add em dashes, emoji, middots, or any other Rule 0 violation - this repo's README has already had a Rule 0 pass and must stay clean. ASCII only.
…e in README.md for the OWASP GenAI Data Security Initiative contribution: PR #62 (GenAI-Security-Project/GenAI-Data-Security-Initiative#62), merged 2026-09-15, defining the agent tool-exchange trace dataset's schema and validation tooling. This is a SEPARATE credit from the existing 'OWASP GenAI Data Security Best Practices v2' row - a different sub-project, a different repository, do not merge them into one row or edit the existing row. Insert the new row immediately after the existing 'OWASP GenAI Data Security Best Practices v2' row (same table, same column widths, same markdown table syntax), matching this exact shape and badge convention (Contributor badge, green #10b981, style=for-the-badge, matching the FIASSE and Best Practices v2 rows): | **OWASP GenAI Data Security Initiative** | [](https://github.com/GenAI-Security-Project/GenAI-Data-Security-Initiative) | Defined the data schema and validation tooling for the agent tool-exchange trace dataset ([PR #62](GenAI-Security-Project/GenAI-Data-Security-Initiative#62)): a JSON Schema, a validator covering the span graph, provenance and citation form, and a CI workflow that runs it on every change | Do not touch any other row, section, or file. Do not add em dashes, emoji, middots, or any other Rule 0 violation - this repo's README has already had a Rule 0 pass and must stay clean. ASCII only.
Summary
Defines the data format for
datasets/agentdataflow_toolexchange_traces/, which has been open for contributions with a<!-- TODO: Define schema once initial data is contributed -->where its format should be, and ships the tooling that enforces it.Split as requested: this PR is the format. The two seed entries and
index.csvmove to a follow-up that depends on it.schema.jsonvalidate.pybuild_index.pyexploit_dataset;index.csvships with the entriesexample.jsonvalidate.pyREADME.md.github/workflows/dataset-agentdataflow-validate.ymlvalidate.pyand checksindex.csvis current, path-filtered to this datasetWhat changed since the last review
The self-citation is gone. The previous revision backed every trace precondition with Zenodo record
10.5281/zenodo.21449149and two mcpindex.ai pages. That record is my own work, and the description should have said so. It called it a public CC BY 4.0 contract-drift corpus and left the ownership out. Those citations are out ofexample.jsonhere, and out of both entries in the follow-up.Preconditions are now cited to the Model Context Protocol specification (2025-06-18), its schema reference, and CVE-2025-54136:
destructiveHintis defined in the MCP schema reference (ToolAnnotations) as a plain server-declared boolean, true meaning the tool may perform destructive updates and false meaning it is additive-only, defaulting to true. Re-declaring it is within what the server is always allowed to do; nothing about it is a client-verified fact.notifications/tools/list_changed, a SHOULD from servers that declaredlistChanged. A client holding a binding from an earliertools/listhas neither a guaranteed notification nor a version to compare.outputSchemaobligations attach to the current schema on both sides: servers MUST conform to it, clients SHOULD validate against it. A client validating against what the server publishes now cannot see that the schema is wider than the one the agent bound to.No prevalence claim survives anywhere. Nothing in the dataset now says how often any of this happens in the wild, only what the protocol permits and that one instance of the class reached shipped software.
Three schema and validator changes so the same class of mistake is harder to make, by me or by the next contributor:
provenance.evidence[].supportsis required. A citation cannot be dropped in without stating the narrow claim it backs.provenance.tier'sderived_from_public_observationis renamed toderived_from_public_documentation. Most of what backs these two entries is a normative reading of a specification, what a protocol permits, not an observation of what happened, and pooling those under a tier named "observation" overstates what the evidence supports. The definition now says the source can be normative or empirical, as long as it's independently checkable.validate.pycannot check it. The earlier draft pointed at a repository-wide vendor-neutrality policy that does not exist as a document today; I said the rule plainly in both the README and the schema description instead.validate.pyalso checks that a citation looks like the kind it claims to be (doi,cve,ghsa,url), catches a span that names itself as its own parent, checks ISO 8601 on every spantimestampin addition todate_added/date_observed(hand-rolled, so it works without the optionalrfc3339-validatorpackage), scans for internal-hostname suffixes (.internal/.corp/.lan/.intra) and credential-shapedkey=valueassignments in addition to the original patterns, flags two or more addresses in one string that look like real routable public IPv4s (RFC 5737 documentation ranges excluded on purpose), makes a backwardst_offset_msan error (it was a silent warning), and prints a warning when the shared taxonomy file is missing (it used to no-op silently). It passes withentries/empty, so this PR stands on its own, and the new CI workflow runs it on every push and PR to the dataset so that's true of the merged tree too, not only of whoever ran it locally.Schema direction, still your call
data_validation/schemas/agentdataflow_trace.schema.jsonexists and describes this dataset. So does the dataset-local schema here, and #67 takes the opposite position on which is authoritative.The repo already has two schema systems and they already disagree, before either PR.
datasets/exploit_dataset/ships its ownschema.jsonwith seven live entries validated against it, and those same entries faildata_validation/schemas/exploit.schema.json. Taking the mergedentries/AML.T0010.jsonagainst the central schema:The central schemas describe an earlier design that the dataset-local ones superseded in practice. The dataset-local one is the one with data behind it, and that is the precedent this PR follows.
What I aligned: the
categoryenum uses the central schema's values (delegation_chain->multi_agent_delegation,plugin_exchange->plugin_data_exchange,memory_io->memory_read_write).What I kept, and why:
trace_id^TRACE-\d{4}$DSGAI-TRACE-<slug>exploit_datasetabandonedEXP-\d{4}for the same reason.trace_data, array of untyped objectsspans, typedtype: benign / adversarialdisposition, plusunintentional_failuresecurity_observations: stringsensitivity_annotations: top-level arraydata_classes+sensitivityThe three options from the earlier description still stand:
data_validation/schemas/agentdataflow_trace.schema.jsonwith a pointer to the dataset-local one, and file an issue recording the same drift forexploit_dataset.I suggested 2 and I am happy to do the work. I have no objection to #67 being the PR you take this from instead. If the answer is the central schema, the entries re-target it and the evidence rules above apply either way.
Provenance tiers
Unchanged in purpose from the earlier description, renamed as noted above. The people holding real agent traces mostly cannot publish them, because production telemetry contains exactly what the anonymization rule forbids. A dataset that accepts only production telemetry stays empty; one that accepts anything fills with plausible fiction.
observed_production- sanitized telemetry from a real deploymentobserved_lab- sandbox, test environment, CTF, red-team exercisederived_from_public_documentation- the mechanism rendered as a trace, its precondition carried as independently checkable evidence, normative or empiricalhypothetical- illustrative only, excluded from any statisticEvery tier except
hypotheticalrequires at least one evidence item, enforced byvalidate.py, and each item has to state what it supports.Two smaller choices, both easy to reverse
sanitization.attestationisconst: true, so an entry cannot merge with the anonymization box quietly unticked, andvalidate.pyscans for credential-shaped, PII-shaped, and now internal-hostname-shaped values independently of the attestation. The scan covers the whole entry:summary,notes,collection_method, and evidencelocatorare where a real hostname actually gets typed, because that is the prose someone writes while looking at a live trace. It is pattern matching and not a guarantee; the README now says so plainly rather than implying full coverage. Related to The PR template asks contributors to certify a validation script that cannot fail #61.mitre_atlas_mappingis release-pinned:{atlas_release, techniques: [{id, name_at_release}]}rather than a flat list of identifiers. Correct eleven MITRE ATLAS technique citations #59 and ATLAS mappings needing a judgement call, and an offer of the verifier behind #59 #60 make the case better than I could. Happy to flatten it to matchexploit_datasetif you would rather have cross-dataset consistency than the pin.Verification
With the follow-up entries branch checked out on top:
Sixteen deliberately broken copies, each one error and a non-zero exit, no false positives on the real files: a span naming itself as its own parent, a backwards
t_offset_ms(now an error, was a warning), acve-typed citation that is not a CVE id, aurl-typed citation that is not an absolute URL, an evidence item with nosupports, an internal hostname (prod-db-07.corp.internal) in a span summary, a credential-shaped assignment (password=hunter2) in prose, two distinct addresses that look like real routable IPv4s in one string, a malformed spantimestamp, plus the original leaked bearer token, RFC1918 address, email, home path, non-ISOdate_added, off-enumcategory, missingevidenceon a derived-tier entry,attestation: false, and filename/trace_idmismatch. A missing taxonomy file now prints a warning instead of silently skipping the check, and RFC 5737 documentation-range IPs (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24) were confirmed NOT to trip the new IP scan, since those are the correct way to write a fake-but-obviously-fake address in an example.data_validation/run_all_checks.pyis still a stub, so the dataset-local validator is what ran, matching theexploit_datasetpattern, and it now also runs in CI on every PR to this dataset.Checklist
example.jsonis fully synthetic, payloads are typed placeholders by convention, andvalidate.pyenforces it across the whole file. The only third party named anywhere is the vendor in the cited CVE, which is already public._shared/dsgai_taxonomy.json, and every span-level finding must map to a risk the trace declares.entries/<trace_id>.json, filename agreement enforced by the validator.LICENSEstill readsNOASSERTIONand #44 is in draft. Which license should I take this contribution as being accepted under? Everything here is original work written for this repo, so I do not think anything is blocked either way.Separately: every validator in
data_validation/validators/is currently a stub with a# TODO(schema_validator,anonymization_scanner,dsgai_mapping_check,completeness_check). Thevalidate.pyhere implements roughly what those four promise, and now runs in CI, so lifting it intodata_validation/would close #61's underlying complaint. Separate PR, if you want it.